DevOps

CI/CD com GitHub Actions: do zero ao deploy na AWS

Atualizado em Março 2026

CI/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.

oidc-role.tf (Terraform)
# 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:

.github/workflows/deploy.yml
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.
security-scan-step.yml
# 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.

Fale Conosco
Assessment gratuito do seu ambiente AWS