A qualidade de um projeto de software não depende apenas de escrever um bom código. Também é fundamental garantir que as alterações desenvolvidas sejam testadas, validadas e disponibilizadas de maneira segura e consistente.
Em equipes de desenvolvimento modernas, realizar manualmente todas essas etapas pode consumir muito tempo, aumentar a possibilidade de erros e dificultar a frequência das entregas.
É justamente nesse cenário que entram as práticas de CI/CD (Continuous Integration e Continuous Delivery/Deployment).
Com um pipeline bem estruturado, desenvolvedores conseguem automatizar tarefas como restauração de dependências, compilação, execução de testes automatizados, geração de artefatos e publicação de aplicações.
No ecossistema .NET, existem diversas ferramentas que permitem implementar esse processo, como GitHub Actions e Azure DevOps.
Neste artigo, vamos entender como funciona CI/CD, quais são as etapas de um pipeline eficiente e como implementar uma automação prática para projetos desenvolvidos em .NET.
CI/CD é um conjunto de práticas utilizadas para automatizar e melhorar o processo de integração, validação e entrega de software.
A sigla representa três conceitos importantes:
CI (Continuous Integration): integração contínua.
CD (Continuous Delivery): entrega contínua.
CD (Continuous Deployment): implantação contínua.
Embora estejam relacionados, cada conceito possui uma responsabilidade específica.
A integração contínua consiste em integrar frequentemente as alterações desenvolvidas por diferentes programadores em um repositório compartilhado.
Sempre que uma alteração é enviada para o repositório, um processo automatizado é executado para verificar se o código continua funcionando corretamente.
Em um projeto .NET, esse processo normalmente envolve:
Recuperar o código-fonte do repositório.
Configurar o SDK do .NET.
Restaurar os pacotes NuGet.
Compilar a aplicação.
Executar os testes automatizados.
Gerar relatórios de validação.
O principal objetivo é identificar problemas rapidamente, antes que eles cheguem a outros ambientes ou sejam incorporados à versão principal da aplicação.
A entrega contínua amplia o processo de integração.
Depois que o código passa pelas validações automatizadas, uma versão pronta para implantação é gerada e disponibilizada como artefato.
Esse artefato pode ser utilizado posteriormente para realizar o deploy em ambientes como:
Desenvolvimento (Development).
Homologação (Staging).
Produção (Production).
Na entrega contínua, a implantação em produção normalmente depende de uma aprovação ou autorização específica.
Na implantação contínua, o processo é ainda mais automatizado.
Depois que todas as validações são aprovadas, a aplicação pode ser publicada automaticamente em produção, sem a necessidade de uma aprovação manual para cada versão.
Essa abordagem exige um nível elevado de confiança nos testes automatizados, nos mecanismos de monitoramento e nos procedimentos de rollback.
Projetos desenvolvidos em .NET podem possuir diferentes níveis de complexidade.
Uma aplicação ASP.NET Core pode depender de APIs externas, bancos de dados, serviços de mensageria, bibliotecas NuGet e outros componentes.
À medida que o projeto cresce, validar manualmente todas essas dependências e funcionalidades se torna cada vez mais trabalhoso.
A implementação de CI/CD oferece algumas vantagens importantes.
A compilação e os testes são executados automaticamente a cada alteração.
Isso permite identificar problemas antes que o código seja integrado à versão principal.
Todos os desenvolvedores utilizam o mesmo processo de validação.
Independentemente de quem realizou a alteração, as mesmas verificações são executadas pelo pipeline.
Tarefas repetitivas deixam de depender de execução manual.
Com isso, a equipe pode dedicar mais tempo ao desenvolvimento de funcionalidades e à resolução de problemas.
A geração de artefatos padronizados permite que o mesmo pacote validado seja utilizado nos diferentes ambientes.
Isso reduz diferenças entre aquilo que foi testado e aquilo que será disponibilizado aos usuários.
Quando uma alteração provoca uma falha na compilação ou nos testes, o pipeline apresenta informações que ajudam a identificar a origem do problema.
Existem diferentes ferramentas para implementar pipelines de integração e entrega contínua.
A escolha depende da infraestrutura, das tecnologias utilizadas e das necessidades da equipe.
As duas alternativas bastante utilizadas no ecossistema .NET são GitHub Actions e Azure DevOps.
O GitHub Actions permite criar fluxos de automação diretamente nos repositórios hospedados no GitHub.
Os pipelines são definidos por meio de arquivos YAML armazenados no diretório:
.github/workflows/
Entre suas principais características estão:
Integração direta com repositórios GitHub.
Execução automática a partir de eventos do repositório.
Suporte a diferentes sistemas operacionais.
Integração com testes e ferramentas de qualidade.
Publicação de artefatos.
Integração com serviços de cloud.
É uma excelente alternativa para projetos pessoais, aplicações corporativas e equipes que já utilizam GitHub para gerenciamento do código-fonte.
Documentação oficial: https://docs.github.com/en/actions
O Azure DevOps oferece um conjunto de ferramentas para gerenciamento do ciclo de vida de desenvolvimento de software.
Dentro desse ecossistema, o Azure Pipelines é responsável pela automação de processos de CI/CD.
Ele permite criar pipelines utilizando arquivos YAML ou interfaces visuais.
Entre suas funcionalidades estão:
Integração com repositórios Git.
Compilação e testes automatizados.
Publicação de artefatos.
Gerenciamento de ambientes.
Aprovações para implantação.
Integração com Azure App Service, Azure Functions e outros serviços.
Uma vantagem importante é a possibilidade de centralizar diferentes etapas do processo de desenvolvimento em uma única plataforma.
Documentação oficial: https://learn.microsoft.com/en-us/azure/devops/pipelines/
As duas ferramentas conseguem atender muito bem projetos .NET.
Uma comparação simplificada:
| Critério | GitHub Actions | Azure DevOps |
|---|---|---|
| Integração com GitHub | Nativa | Disponível |
| Configuração YAML | Sim | Sim |
| Build e testes .NET | Sim | Sim |
| Publicação de artefatos | Sim | Sim |
| Aprovação de ambientes | Sim | Sim |
| Integração com Azure | Sim | Sim |
| Gerenciamento de projetos | Ecossistema GitHub | Azure Boards |
| Execução em agentes próprios | Sim | Sim |
Minha recomendação é utilizar GitHub Actions quando o repositório já está hospedado no GitHub e a equipe deseja uma automação simples e integrada.
Já o Azure DevOps pode ser especialmente interessante para organizações que utilizam Azure Boards, Azure Repos e outros recursos da plataforma.
Antes de começar a implementação, é importante definir quais etapas devem fazer parte do processo.
Um pipeline eficiente não precisa ser extremamente complexo. O mais importante é garantir que as validações necessárias sejam executadas na ordem correta.
Uma estrutura inicial pode ser dividida nas seguintes etapas:
O primeiro passo é recuperar o código-fonte do repositório.
O pipeline precisa acessar a versão que será validada para executar as próximas etapas.
É necessário disponibilizar a versão correta do SDK do .NET.
Isso evita problemas de compatibilidade entre o ambiente utilizado pelo desenvolvedor e o ambiente de execução do pipeline.
Uma boa prática é manter a versão do SDK documentada no arquivo global.json.
O comando dotnet restore realiza a restauração das dependências declaradas nos projetos.
Essa etapa garante que os pacotes NuGet necessários estejam disponíveis para a compilação.
Com as dependências restauradas, o próximo passo é compilar a solução.
O comando dotnet build identifica problemas de compilação e verifica se o código pode ser transformado em uma aplicação executável.
Depois da compilação, o pipeline deve executar os testes automatizados.
Em projetos .NET, podemos utilizar ferramentas como:
xUnit.
NUnit.
MSTest.
Os testes devem validar as principais regras de negócio, comportamentos esperados e integrações relevantes.
Dependendo da maturidade do projeto, podemos adicionar verificações complementares:
Análise estática de código.
Verificação de vulnerabilidades em dependências.
Análise de cobertura de testes.
Verificação de padrões de codificação.
Análise de qualidade com SonarQube.
Essas verificações ajudam a identificar problemas que não necessariamente são encontrados durante a compilação.
Se todas as etapas anteriores forem concluídas com sucesso, podemos utilizar o comando dotnet publish.
Esse comando prepara os arquivos necessários para disponibilizar a aplicação em outro ambiente.
A última etapa consiste em disponibilizar a aplicação no ambiente de destino.
Dependendo da infraestrutura, podemos realizar a publicação em:
Azure App Service.
Azure Functions.
Máquinas virtuais.
Containers Docker.
Kubernetes.
Servidores Windows com IIS.
Uma boa estratégia é separar o processo de build e validação do processo de implantação.
Dessa maneira, conseguimos utilizar o mesmo artefato em diferentes ambientes.
Agora vamos colocar os conceitos em prática.
Imagine que temos uma solução desenvolvida em ASP.NET Core, organizada da seguinte maneira:
MinhaAplicacao/
│
├── MinhaAplicacao.sln
│
├── MinhaAplicacao.Api/
│ ├── Controllers/
│ ├── Services/
│ └── MinhaAplicacao.Api.csproj
│
├── MinhaAplicacao.Domain/
│ └── MinhaAplicacao.Domain.csproj
│
└── MinhaAplicacao.Tests/
└── MinhaAplicacao.Tests.csproj
Nosso objetivo é criar um pipeline que execute automaticamente:
Recuperação do código.
Configuração do SDK .NET.
Restauração das dependências.
Compilação da solução.
Execução dos testes.
Publicação da aplicação.
Armazenamento do artefato gerado.
Para este exemplo, utilizaremos .NET 10, uma versão LTS disponível em outubro de 2026. Caso seu projeto utilize outra versão suportada, ajuste a configuração do SDK conforme necessário.
Dentro do repositório, crie a seguinte estrutura:
.github/
└── workflows/
└── dotnet-ci.yml
O arquivo dotnet-ci.yml será responsável por definir as etapas de automação.
Vamos utilizar a seguinte implementação:
name: .NET CI Pipeline
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
- name: Restore dependencies
run: dotnet restore MinhaAplicacao.sln
- name: Build solution
run: >
dotnet build MinhaAplicacao.sln
--configuration Release
--no-restore
- name: Run tests
run: >
dotnet test MinhaAplicacao.sln
--configuration Release
--no-build
--logger "trx;LogFileName=test-results.trx"
- name: Publish application
run: >
dotnet publish MinhaAplicacao.Api/MinhaAplicacao.Api.csproj
--configuration Release
--no-restore
--output ./publish
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: minha-aplicacao
path: ./publish
retention-days: 7
Importante: os caminhos utilizados no exemplo devem ser ajustados conforme a estrutura real da sua solução.
Vamos analisar os principais pontos da implementação.
on:
push:
branches:
- main
- develop
pull_request:
branches:
- main
- develop
Essa configuração determina quando o pipeline será executado.
Sempre que alguém enviar alterações para as branches main ou develop, a automação será iniciada.
O mesmo acontecerá quando uma Pull Request for criada ou atualizada tendo essas branches como destino.
Isso permite validar o código antes de sua integração definitiva.
- name: Setup .NET
uses: actions/setup-dotnet@v6
with:
dotnet-version: '10.0.x'
Aqui utilizamos a Action oficial setup-dotnet para disponibilizar o SDK do .NET 10.
A utilização de uma versão explicitamente configurada evita que o pipeline dependa de uma versão diferente daquela utilizada pelo projeto.
- name: Restore dependencies
run: dotnet restore MinhaAplicacao.sln
Essa etapa restaura os pacotes necessários para todos os projetos da solução.
- name: Build solution
run: >
dotnet build MinhaAplicacao.sln
--configuration Release
--no-restore
O parâmetro --configuration Release determina que a compilação será realizada utilizando a configuração Release.
Já o parâmetro --no-restore evita repetir a restauração das dependências, uma vez que essa etapa já foi executada anteriormente.
- name: Run tests
run: >
dotnet test MinhaAplicacao.sln
--configuration Release
--no-build
Essa etapa executa os testes automatizados existentes na solução.
O parâmetro --no-build evita uma nova compilação, aproveitando os arquivos gerados anteriormente.
Caso algum teste falhe, o pipeline será interrompido e apresentará o resultado da execução.
- name: Publish application
run: >
dotnet publish MinhaAplicacao.Api/MinhaAplicacao.Api.csproj
--configuration Release
--no-restore
--output ./publish
O comando dotnet publish prepara a aplicação para distribuição.
Os arquivos gerados serão armazenados no diretório publish.
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: minha-aplicacao
path: ./publish
retention-days: 7
Por último, utilizamos a Action upload-artifact para armazenar os arquivos publicados como um artefato do pipeline.
Esse pacote poderá ser recuperado posteriormente para realizar o deploy em outro ambiente.
O resultado é um processo automatizado de integração contínua que valida o código e prepara uma versão da aplicação para implantação.
Até aqui, implementamos um processo de CI que compila, testa e gera um artefato.
Para completar o fluxo de CD, precisamos adicionar uma etapa responsável por disponibilizar a aplicação no ambiente de destino.
Uma arquitetura interessante é separar os processos em dois momentos:
Pipeline de CI:
Executa a cada alteração.
Compila a solução.
Executa os testes.
Gera o artefato.
Armazena a versão produzida.
Pipeline de CD:
Recupera o artefato aprovado.
Define o ambiente de destino.
Executa as configurações necessárias.
Realiza a implantação.
Verifica se a aplicação está funcionando.
Por exemplo, podemos configurar um ambiente chamado Production no GitHub Actions.
Esse ambiente pode possuir regras de proteção, como aprovações obrigatórias antes da implantação.
Também é possível armazenar informações sensíveis, como credenciais e configurações de acesso, utilizando os mecanismos de Secrets e Variables.
Uma recomendação importante é evitar armazenar senhas, connection strings ou tokens diretamente nos arquivos YAML ou no código-fonte.
Para aplicações hospedadas no Azure, uma alternativa interessante é utilizar autenticação por OpenID Connect (OIDC), evitando manter credenciais permanentes de acesso à cloud.
Outro cuidado importante é garantir que a implantação utilize exatamente o artefato produzido e validado na etapa de CI, sem realizar uma nova compilação desnecessária.
Criar um pipeline funcional é apenas o primeiro passo.
Para que ele seja realmente eficiente, precisamos considerar alguns aspectos relacionados à manutenção, segurança e desempenho.
Evite depender automaticamente da versão mais recente do SDK disponível no agente de execução.
Utilize um arquivo global.json para definir a versão esperada pelo projeto.
Isso ajuda a manter a consistência entre os ambientes de desenvolvimento e automação.
A restauração de dependências pode representar uma parte significativa do tempo de execução de um pipeline.
Uma alternativa é utilizar mecanismos de cache para reaproveitar os pacotes já baixados.
No GitHub Actions, o setup-dotnet oferece suporte a cache de dependências NuGet, utilizando arquivos packages.lock.json.
Essa abordagem pode reduzir o tempo gasto na restauração, especialmente em soluções com muitos projetos e dependências.
Nenhuma aplicação deveria ser publicada em produção sem passar pelas validações necessárias.
O pipeline deve interromper o processo quando uma etapa crítica falhar.
Isso evita que uma versão com problemas conhecidos seja disponibilizada aos usuários.
Evite utilizar configurações de desenvolvimento diretamente em produção.
Cada ambiente deve possuir suas próprias configurações, credenciais e recursos.
Por exemplo:
| Configuração | Development | Staging | Production |
|---|---|---|---|
| Banco de dados | Dev DB | Homolog DB | Prod DB |
| Logging | Debug | Information | Warning/Information |
| Dados de teste | Sim | Controlados | Não |
| Aprovação de deploy | Não | Opcional | Recomendada |
A configuração exata dos níveis de logging e dos recursos deve considerar as necessidades de monitoramento da aplicação.
Informações sensíveis nunca devem ser armazenadas diretamente no repositório.
Utilize os recursos de gerenciamento de Secrets da plataforma de CI/CD.
Isso inclui:
Senhas.
Tokens de autenticação.
Connection strings.
Chaves de APIs.
Credenciais de cloud.
Sempre que possível, prefira identidades gerenciadas ou mecanismos de autenticação federada.
Ferramentas como SonarQube podem complementar o processo de validação.
Elas permitem identificar problemas relacionados a:
Complexidade do código.
Duplicação.
Vulnerabilidades.
Code smells.
Cobertura de testes.
A análise de qualidade ajuda a evitar que problemas técnicos se acumulem ao longo do desenvolvimento.
Mesmo com um pipeline bem estruturado, problemas podem ocorrer após uma implantação.
Por isso, é importante ter uma estratégia para retornar rapidamente à versão anterior.
Algumas possibilidades incluem:
Manter versões anteriores dos artefatos.
Utilizar slots de implantação.
Trabalhar com versões de containers.
Implementar estratégias Blue-Green Deployment.
Utilizar Rollback automatizado quando aplicável.
A estratégia deve considerar também possíveis alterações incompatíveis no banco de dados.
O processo não termina quando o deploy é concluído.
É importante verificar se a aplicação está respondendo corretamente.
Podemos utilizar:
Health Checks.
Logs estruturados.
Application Insights.
Alertas de disponibilidade.
Monitoramento de erros e desempenho.
Essas informações permitem identificar rapidamente problemas que não foram detectados durante os testes automatizados.
Embora os exemplos apresentados utilizem .NET 10, os conceitos de CI/CD também se aplicam a projetos desenvolvidos com .NET Framework.
Entretanto, existem algumas diferenças importantes.
Projetos .NET Framework normalmente possuem dependências de componentes do Windows e versões específicas do Visual Studio Build Tools.
Por esse motivo, pode ser necessário utilizar agentes Windows na execução do pipeline.
Além disso, dependendo da estrutura do projeto, a compilação pode exigir ferramentas como MSBuild e procedimentos específicos para publicação de aplicações ASP.NET tradicionais.
O ponto principal é entender que CI/CD não está limitado a aplicações desenvolvidas com .NET moderno.
Mesmo sistemas legados podem se beneficiar da automação de compilação, testes e implantação.
A implementação de CI/CD representa um passo importante para melhorar a qualidade e a eficiência do desenvolvimento de software.
No ecossistema .NET, ferramentas como GitHub Actions e Azure DevOps permitem automatizar praticamente todas as etapas necessárias para validar, preparar e disponibilizar uma aplicação.
Começar com um pipeline simples, contendo restauração de dependências, compilação e testes automatizados, já proporciona benefícios significativos.
À medida que o projeto evolui, podemos adicionar análise de qualidade, geração de artefatos, implantação automatizada, aprovações e monitoramento.
O mais importante é construir um processo consistente, seguro e adequado à realidade da equipe.
Com um pipeline bem estruturado, os desenvolvedores conseguem reduzir tarefas manuais, identificar problemas mais rapidamente e entregar novas funcionalidades com maior confiança.
Se você trabalha com C# e .NET, vale a pena começar a implementar essas práticas nos seus projetos. Mesmo uma pequena automação pode fazer uma grande diferença no processo de desenvolvimento.