CI/CD com GitHub Actions: do zero ao deploy na AWS
Atualizado em Março 2026CI/CD (Continuous Integration / Continuous Delivery) é a prática de automatizar o processo de integração, teste e entrega de software, permitindo que equipes deployem código com mais velocidade, segurança e confiabilidade. O GitHub Actions é uma plataforma de CI/CD integrada ao GitHub que permite criar pipelines automatizados usando workflows definidos em YAML.
Integração Contínua (CI) e Entrega Contínua (CD) são práticas fundamentais para equipes que buscam entregar software de qualidade com velocidade e segurança. O GitHub Actions se consolidou como uma das plataformas de CI/CD mais populares do mercado, oferecendo integração nativa com repositórios GitHub, milhares de actions prontas da comunidade é uma experiência de configuração intuitiva baseada em YAML.
Neste tutorial completo, vamos construir um pipeline CI/CD profissional que testa, builda e deploya uma aplicação containerizada na AWS ECS com Fargate, seguindo as melhores práticas de segurança e confiabilidade.
Conceitos fundamentais
Antes de mergulhar no código, vamos alinhar os conceitos:
- Continuous Integration (CI): prática de integrar código frequentemente, com builds e testes automatizados a cada push. O objetivo é detectar erros cedo.
- Continuous Delivery (CD): extensão do CI onde o código aprovado é automaticamente preparado para deploy em produção, mas requer aprovação manual para o release final.
- Continuous Deployment: vai além do Delivery: todo código que passa nos testes é automaticamente deployado em produção, sem intervenção humana.
Na prática, a maioria das empresas implementa Continuous Delivery (deploy automático até staging, com aprovação manual para produção) como um equilíbrio entre velocidade e controle.
Arquitetura do pipeline
O pipeline que vamos construir segue estas etapas:
- Trigger: push para main ou pull request
- CI: lint, testes unitários, testes de integração
- Build: construção da imagem Docker e push para Amazon ECR
- Deploy Staging: deploy automático no ambiente de staging
- Aprovação: gate manual para produção
- Deploy Produção: deploy com rolling update no ECS
Configuração de segurança: autenticação OIDC com AWS
A forma mais segura de autenticar GitHub Actions com a AWS e usando OIDC (OpenID Connect), seguindo principios de Zero Trust e IAM seguro. em vez de access keys estáticas. Com OIDC, o GitHub assume uma IAM Role temporária sem necessidade de armazenar credenciais.
# IAM Role para GitHub Actions com OIDC
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["sts.amazonaws.com"]
thumbprint_list = ["6938fd4d98bab03faadb97b34396831e3780aea1"]
}
resource "aws_iam_role" "github_actions" {
name = "github-actions-deploy"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Principal = {
Federated = aws_iam_openid_connect_provider.github.arn
}
Action = "sts:AssumeRoleWithWebIdentity"
Condition = {
StringEquals = {
"token.actions.githubusercontent.com:aud" = "sts.amazonaws.com"
}
StringLike = {
"token.actions.githubusercontent.com:sub" = "repo:sua-org/seu-repo:*"
}
}
}
]
})
}
# Permissões para ECR e ECS
resource "aws_iam_role_policy_attachment" "ecr_push" {
role = aws_iam_role.github_actions.name
policy_arn = "arn:aws:iam::policy/AmazonEC2ContainerRegistryPowerUser"
}
resource "aws_iam_role_policy" "ecs_deploy" {
name = "ecs-deploy"
role = aws_iam_role.github_actions.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ecs:UpdateService",
"ecs:DescribeServices",
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition",
"ecs:DescribeTasks",
"iam:PassRole"
]
Resource = "*"
}
]
})
}
O pipeline completo
Agora vamos ao workflow principal do GitHub Actions. Crie o arquivo .github/workflows/deploy.yml no seu repositório:
name: CI/CD Pipeline
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
AWS_REGION: sa-east-1
ECR_REPOSITORY: minha-app
ECS_CLUSTER: produção
ECS_SERVICE: minha-app-service
CONTAINER_NAME: web
permissions:
id-token: write
contents: read
jobs:
# ========== CI: Testes ==========
test:
name: Testes e Lint
runs-on: ubuntu-latest
steps:
- name: Checkout código
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Instalar dependências
run: npm ci
- name: Executar lint
run: npm run lint
- name: Executar testes unitários
run: npm test -- --coverage
- name: Upload relatório de cobertura
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
# ========== Build e Push para ECR ==========
build:
name: Build e Push Docker
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
outputs:
image: ${{ steps.build-image.outputs.image }}
steps:
- name: Checkout código
uses: actions/checkout@v4
- name: Configurar credenciais AWS (OIDC)
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::role/github-actions-deploy
aws-region: ${{ env.AWS_REGION }}
- name: Login no Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: Build, tag e push imagem
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build \
--build-arg BUILD_DATE=$(date -u +%Y-%m-%dT%H:%M:%SZ) \
--build-arg GIT_SHA=${{ github.sha }} \
-t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG \
-t $ECR_REGISTRY/$ECR_REPOSITORY:latest \
.
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
echo "image=$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG" >> $GITHUB_OUTPUT
# ========== Deploy Staging ==========
deploy-staging:
name: Deploy Staging
needs: build
runs-on: ubuntu-latest
environment: staging
steps:
- name: Checkout código
uses: actions/checkout@v4
- name: Configurar credenciais AWS
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::role/github-actions-deploy
aws-region: ${{ env.AWS_REGION }}
- name: Atualizar task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition-staging.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ needs.build.outputs.image }}
- name: Deploy no ECS Staging
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}-staging
cluster: staging
wait-for-service-stability: true
# ========== Deploy Produção (com aprovação) ==========
deploy-production:
name: Deploy Produção
needs: [build, deploy-staging]
runs-on: ubuntu-latest
environment: production # Requer aprovação manual
steps:
- name: Checkout código
uses: actions/checkout@v4
- name: Configurar credenciais AWS
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::role/github-actions-deploy
aws-region: ${{ env.AWS_REGION }}
- name: Atualizar task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition-prod.json
container-name: ${{ env.CONTAINER_NAME }}
image: ${{ needs.build.outputs.image }}
- name: Deploy no ECS Produção
uses: aws-actions/amazon-ecs-deploy-task-definition@v2
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: ${{ env.ECS_SERVICE }}
cluster: ${{ env.ECS_CLUSTER }}
wait-for-service-stability: true
- name: Notificar deploy no Slack
if: success()
uses: slackapi/slack-github-action@v1.27
with:
payload: |
{
"text": "Deploy produção realizado com sucesso! Commit: ${{ github.sha }}"
}
env:
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
Entendendo cada etapa
Job: test
Executado em todo push e pull request. Instala dependências, roda lint (verificação de estilo de código) e testes unitários com cobertura. Se qualquer etapa falhar, o pipeline para. Isso garante que código com bugs não avance.
Job: build
Executado apenas em push para main (não em PRs). Usa OIDC para autenticar na AWS sem credenciais estáticas, builda a imagem Docker com SHA do commit como tag (garantindo rastreabilidade), e faz push para o ECR.
Job: deploy-staging
Atualiza a task definition do ECS com a nova imagem e deploya no cluster de staging. O wait-for-service-stability garante que o deploy foi bem-sucedido antes de prosseguir.
Job: deploy-production
Usa o recurso de environments do GitHub com proteção de branch. Ao configurar o environment "production" com reviewers obrigatórios, o pipeline aguarda aprovação manual antes de executar. Isso é Continuous Delivery na prática.
Boas práticas de CI/CD com GitHub Actions
1. Use OIDC em vez de access keys
Credenciais estáticas (access keys) armazenadas em secrets são um risco. Se comprometidas, permitem acesso irrestrito à AWS. OIDC gera credenciais temporárias com escopo limitado ao workflow.
2. Pin de versões de actions
Em vez de uses: actions/checkout@v4, use o SHA completo: uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11. Isso protege contra supply chain attacks caso uma action seja comprometida.
3. Cache de dependências
Use cache para npm, pip, Maven, etc. Isso pode reduzir o tempo de CI de minutos para segundos. O setup-node com cache: 'npm' já faz isso automaticamente.
4. Environments com proteção
Configure environments (staging, production) com regras de proteção: reviewers obrigatórios, wait timer, restrição por branch. Isso garante governança sem sacrificar automação.
5. Notificações inteligentes
Configure notificações para Slack, Teams ou e-mail apenas para eventos relevantes: deploys em produção, falhas de pipeline. Evite notificações de cada push para não gerar "alert fatigue".
6. Rollback automatizado
Implemente health checks pós-deploy. Se a nova versão falhar, o ECS automaticamente reverte para a versão anterior com a configuração de deployment circuit breaker.
"Um bom pipeline de CI/CD é aquele que os desenvolvedores confiam. Se o time faz merge sem medo porque sabe que o pipeline vai detectar problemas é o deploy é seguro, você atingiu o objetivo."
Monitoramento pós-deploy
O pipeline não termina no deploy. Configure monitoramento para validar que a nova versão está funcionando corretamente:
- CloudWatch Alarms: monitore taxa de erros 5xx, latência P99 e health checks
- Canary deployments: use weighted target groups no ALB para rotear apenas 10% do tráfego para a nova versão inicialmente
- Synthetic monitoring: configure CloudWatch Synthetics para executar testes end-to-end automaticamente
- Dashboards: mantenha um dashboard centralizado com métricas de deploy (frequência, taxa de sucesso, tempo médio de recovery)
Na Prática: pipeline CI/CD para fintech
Uma fintech com 8 microsserviços em ECS Fargate fazia deploys manuais via console AWS. A média era 1 deploy por semana, com downtime de 15-30 minutos a cada release e pelo menos 1 rollback por mes por erro humano.
- Semana 1: configuramos OIDC para GitHub Actions, eliminando todas as access keys estáticas. Criamos o pipeline base com testes, build Docker e push para ECR.
- Semana 2: adicionamos deploy automático em staging com ECS rolling update e circuit breaker habilitado. Configuramos environment protection no GitHub com 2 aprovadores obrigatórios para produção.
- Semana 3: integramos Trivy para scan de vulnerabilidades na imagem Docker (step adicional entre build e deploy), notificações no Slack para deploys e falhas, e CloudWatch Synthetics para smoke tests pós-deploy.
# Step adicional: scan de vulnerabilidades com Trivy
- name: Scan de vulnerabilidades na imagem Docker
uses: aquasecurity/trivy-action@0.28.0
with:
image-ref: ${{ steps.login-ecr.outputs.registry }}/${{ env.ECR_REPOSITORY }}:${{ github.sha }}
format: 'sarif'
output: 'trivy-results.sarif'
severity: 'CRITICAL,HIGH'
exit-code: '1' # Falha o pipeline se encontrar vulnerabilidades críticas
- name: Upload scan results para GitHub Security
uses: github/codeql-action/upload-sarif@v3
if: always()
with:
sarif_file: 'trivy-results.sarif'
# Step: habilitar circuit breaker no ECS (via Terraform)
# No ecs-service.tf:
# deployment_circuit_breaker {
# enable = true
# rollback = true
# }
Resultado: deployment frequency subiu de 1 para 12 por semana. Lead time for changes caiu de 3 dias para 25 minutos. Change failure rate caiu de 15% para 2%. MTTR caiu de 45 minutos (rollback manual) para 3 minutos (circuit breaker automático).
KPIs para o Decisor (DORA Metrics)
- Deployment frequency: número de deploys em produção por semana. Target: diário ou múltiplas vezes ao dia (elite performers segundo DORA).
- Lead time for changes: tempo entre commit e código rodando em produção. Target: menos de 1 hora para elite, menos de 1 dia para high performers.
- Change failure rate (%): percentual de deploys que causam incidentes. Target: abaixo de 5% (15% é a média do mercado).
- Mean time to recovery (MTTR): tempo para restaurar o serviço após incidente. Target: menos de 10 minutos com rollback automático.
- Pipeline success rate (%): percentual de runs do pipeline que completam com sucesso. Target: acima de 90% (abaixo indica testes instáveis ou flaky tests).
Próximos passos
O pipeline apresentado neste artigo é um ponto de partida sólido. À medida que seu projeto evolui, considere adicionar:
- Testes de integração com banco de dados (usando services do GitHub Actions)
- Análise de segurança com Snyk ou Trivy no build da imagem Docker
- Deploy com Terraform para mudancas de infraestrutura
- Matrix builds para testar em múltiplas versões de runtime
- Performance testing automatizado com k6 ou Artillery
Na RFX Tecnologia, por meio do nosso serviço de DevOps e Automação, implementamos pipelines de CI/CD robustos e seguros para empresas de todos os portes. Desde a configuração inicial até a otimização de pipelines existentes, ajudamos times de desenvolvimento a entregar software com mais velocidade e confiança. Fale conosco para modernizar seu processo de deploy.