Construa CI/CD com GitHub Actions

Intermediário | 120 min leitura | 2025.12.18

O que você aprenderá neste tutorial

  • Conceitos básicos do GitHub Actions
  • Como escrever arquivos de workflow
  • Execução automática de testes
  • Build e armazenamento de artefatos
  • Configuração de deploy por ambiente
  • Gerenciamento de secrets e segurança

Pré-requisitos: Ter uma conta no GitHub. Conhecimento básico de projetos Node.js facilita o entendimento.

O que é CI/CD? Por que é Necessário?

História do CI/CD

O conceito de Integração Contínua (CI) foi sistematizado em 2000 por Martin Fowler e Kent Beck.

“Integração Contínua é uma prática de desenvolvimento de software onde membros da equipe integram seu trabalho frequentemente” — Martin Fowler

Entrega/Deploy Contínuo (CD) estende o CI, automatizando releases para produção.

Evolução das Ferramentas de CI/CD

AnoFerramentaCaracterísticas
2004Hudson/JenkinsOn-premise, baseado em plugins
2011Travis CICloud-based, integração GitHub
2014CircleCISuporte Docker, builds rápidos
2017GitLab CIIntegrado ao GitLab
2019GitHub ActionsIntegrado ao GitHub, marketplace

Vantagens do GitHub Actions

  1. Integrado ao GitHub: Sem necessidade de serviço separado, experiência seamless
  2. Definição YAML: Infraestrutura como Código (IaC)
  3. Marketplace: Actions reutilizáveis abundantes
  4. Tier gratuito: Ilimitado para repositórios públicos
  5. Matrix build: Testes paralelos em múltiplos ambientes

DORA Metrics (Métricas DevOps)

De acordo com a pesquisa do Google (DORA), equipes de alto desempenho alcançam:

MétricaEliteBaixo Desempenho
Frequência de deployMúltiplas vezes/diaMenos de 1x/mês
Lead timeMenos de 1 horaMais de 1 mês
Taxa de falha de mudança0-15%46-60%
Tempo de recuperaçãoMenos de 1 horaMais de 1 semana

CI/CD é um elemento importante para melhorar essas métricas.

Documentação oficial: GitHub Actions Documentation

Step 1: Fundamentos do GitHub Actions

GitHub Actions funciona colocando arquivos YAML no diretório .github/workflows/ do repositório.

Estrutura Básica

# Nome do workflow
name: CI Pipeline

# Trigger (quando executar)
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

# Definição de jobs
jobs:
  job-name:
    runs-on: ubuntu-latest
    steps:
      - name: Step 1
        run: echo "Hello"

Conceitos Principais

ConceitoDescriçãoExemplo
WorkflowProcesso de automação definido em YAMLCI, deploy
EventTrigger que inicia o workflowpush, pull_request, schedule
JobConjunto de steps executados no mesmo runnertest, build, deploy
StepTarefa individualcheckout, npm install
ActionUnidade de tarefa reutilizávelactions/checkout@v4
RunnerServidor que executa o workflowubuntu-latest, windows-latest

Ciclo de Vida do Workflow

flowchart LR
    Event["Event ocorre"] --> Workflow["Workflow inicia"] --> Job["Job executa<br/>(paralelo/sequencial)"] --> Step["Step executa"] --> Done["Concluído"]

    subgraph Events["Tipos de Event"]
        direction TB
        E1["push"]
        E2["pull_request"]
        E3["schedule (cron)"]
        E4["workflow_dispatch (manual)"]
        E5["repository_dispatch (API)"]
    end

    Events -.-> Event

Step 2: Criando o Primeiro Workflow

Estrutura de Diretórios

your-project/
├── .github/
│   └── workflows/
│       └── ci.yml
├── src/
├── package.json
└── README.md

.github/workflows/ci.yml

name: CI

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      # Checkout do repositório
      - name: Checkout repository
        uses: actions/checkout@v4

      # Setup do Node.js
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      # Instalação de dependências
      - name: Install dependencies
        run: npm ci

      # Execução do Lint
      - name: Run linter
        run: npm run lint

      # Execução dos testes
      - name: Run tests
        run: npm test

      # Upload do relatório de cobertura
      - name: Upload coverage
        uses: codecov/codecov-action@v3
        with:
          files: ./coverage/lcov.info

Diferença entre npm ci e npm install

ComandoUsoCaracterísticas
npm ciAmbiente CIUsa package-lock.json estritamente, rápido
npm installAmbiente de desenvolvimentoPrioriza package.json, pode atualizar lock

Melhor prática: Sempre use npm ci em ambientes CI.

Step 3: Matrix Build

Execute testes paralelos em múltiplas versões de Node.js e sistemas operacionais.

name: Matrix Build

on: [push, pull_request]

jobs:
  test:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        os: [ubuntu-latest, windows-latest, macos-latest]
        node-version: [18, 20, 22]
      fail-fast: false  # Continua outros se um falhar

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

      - run: npm ci
      - run: npm test

Exclusão e Inclusão de Matrix

strategy:
  matrix:
    os: [ubuntu-latest, windows-latest]
    node-version: [18, 20]
    # Excluir combinação específica
    exclude:
      - os: windows-latest
        node-version: 18
    # Combinação adicional
    include:
      - os: ubuntu-latest
        node-version: 22
        experimental: true

Step 4: Build e Armazenamento de Artefatos

name: Build and Upload

on:
  push:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install and Build
        run: |
          npm ci
          npm run build

      # Upload de artefatos de build
      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
          retention-days: 7

  # Usar artefatos no próximo job
  deploy:
    needs: build
    runs-on: ubuntu-latest

    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v4
        with:
          name: build-output

      - name: Deploy
        run: |
          echo "Deploying..."
          ls -la

Step 5: Variáveis de Ambiente e Secrets

Como Configurar Secrets

  1. Repositório GitHub → Settings → Secrets and variables → Actions
  2. Clique em “New repository secret”
  3. Digite nome (ex: DEPLOY_TOKEN) e valor

Tipos de Secrets

TipoEscopoUso
Repository secretsRepositório únicoChaves API, tokens
Environment secretsApenas ambiente específicoPara produção/staging
Organization secretsTodos os repositórios da orgContas de serviço comuns
name: Deploy with Secrets

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    environment: production  # Especificar ambiente (fluxo de aprovação também configurável)

    steps:
      - uses: actions/checkout@v4

      # Usar secrets como variáveis de ambiente
      - name: Deploy
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        run: |
          echo "Deploying with API key..."
          ./scripts/deploy.sh

      # Token automático fornecido pelo GitHub
      - name: Create Release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: |
          gh release create v1.0.0

Melhores Práticas de Segurança

  1. Princípio do menor privilégio: Conceder apenas permissões necessárias
  2. Rotação de secrets: Atualizar regularmente
  3. Prevenir exposição em logs: Secrets são automaticamente mascarados
  4. Restringir acesso de forks: Restringir acesso a secrets em PRs
# Definir permissões explicitamente
permissions:
  contents: read
  packages: write
  id-token: write  # Para autenticação OIDC

Nota de segurança: Secrets são automaticamente mascarados como *** nos logs, mas ainda tome cuidado para não expor inadvertidamente.

Step 6: Branches Condicionais e Filtros

name: Conditional Workflow

on:
  push:
    branches: [main, develop]
    # Executar apenas em alterações de paths específicos
    paths:
      - 'src/**'
      - 'package.json'
    # Excluir paths específicos
    paths-ignore:
      - '**.md'
      - 'docs/**'

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm test

  deploy-staging:
    needs: test
    runs-on: ubuntu-latest
    # Executar apenas no branch develop
    if: github.ref == 'refs/heads/develop'
    steps:
      - run: echo "Deploying to staging..."

  deploy-production:
    needs: test
    runs-on: ubuntu-latest
    # Apenas branch main e com tag
    if: github.ref == 'refs/heads/main' && startsWith(github.ref, 'refs/tags/')
    steps:
      - run: echo "Deploying to production..."

  notify-on-failure:
    needs: [test]
    runs-on: ubuntu-latest
    # Executar apenas em falha
    if: failure()
    steps:
      - name: Notify Slack
        run: echo "Tests failed!"

Exemplos de Expressões Condicionais

# Nome do branch
if: github.ref == 'refs/heads/main'

# Tipo de evento
if: github.event_name == 'pull_request'

# Ator
if: github.actor == 'dependabot[bot]'

# Condição composta
if: github.ref == 'refs/heads/main' && github.event_name == 'push'

# Baseado em resultado
if: success()  # Job anterior bem-sucedido
if: failure()  # Job anterior falhou
if: always()   # Sempre executar
if: cancelled() # Quando cancelado

Step 7: Utilizando Cache

Acelere o tempo de build com cache de dependências.

name: Build with Cache

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'  # Cache automático

      # Gerenciamento manual de cache
      - name: Cache node modules
        id: cache-npm
        uses: actions/cache@v4
        with:
          path: node_modules
          key: ${{ runner.os }}-node-${{ hashFiles('package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-node-

      # Instalar se não houver cache
      - name: Install dependencies
        if: steps.cache-npm.outputs.cache-hit != 'true'
        run: npm ci

      - run: npm run build

Estratégias de Cache

AlvoExemplo de KeyRestore Keys
npm${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}${{ runner.os }}-node-
pip${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}${{ runner.os }}-pip-
Gradle${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*') }}${{ runner.os }}-gradle-

Step 8: Workflows Reutilizáveis

Composite Action (Ação Composta)

.github/actions/setup-node-and-install/action.yml

name: 'Setup Node and Install'
description: 'Setup Node.js and install dependencies'

inputs:
  node-version:
    description: 'Node.js version'
    required: false
    default: '20'

runs:
  using: 'composite'
  steps:
    - name: Setup Node.js
      uses: actions/setup-node@v4
      with:
        node-version: ${{ inputs.node-version }}
        cache: 'npm'

    - name: Install dependencies
      run: npm ci
      shell: bash

Exemplo de uso:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-node-and-install
        with:
          node-version: '20'
      - run: npm test

Reusable Workflow (Workflow Reutilizável)

.github/workflows/reusable-test.yml

name: Reusable Test Workflow

on:
  workflow_call:
    inputs:
      node-version:
        required: false
        type: string
        default: '20'
    secrets:
      npm-token:
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}
      - run: npm ci
      - run: npm test

Lado da chamada:

name: CI

on: [push]

jobs:
  call-test:
    uses: ./.github/workflows/reusable-test.yml
    with:
      node-version: '20'
    secrets:
      npm-token: ${{ secrets.NPM_TOKEN }}

Step 9: Workflows de Deploy

Deploy para Vercel

name: Deploy to Vercel

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Deploy to Vercel
        uses: amondnet/vercel-action@v25
        with:
          vercel-token: ${{ secrets.VERCEL_TOKEN }}
          vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
          vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
          vercel-args: '--prod'

Deploy para AWS S3 + CloudFront

name: Deploy to AWS

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    permissions:
      id-token: write
      contents: read

    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials (OIDC)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ secrets.AWS_ROLE_ARN }}
          aws-region: ap-northeast-1

      - name: Build
        run: |
          npm ci
          npm run build

      - name: Deploy to S3
        run: aws s3 sync dist/ s3://${{ secrets.S3_BUCKET }}

      - name: Invalidate CloudFront
        run: |
          aws cloudfront create-invalidation \
            --distribution-id ${{ secrets.CF_DISTRIBUTION_ID }} \
            --paths "/*"

Dicas de Debug

Habilitar Logs de Debug

Configure nos Secrets do repositório:

  • ACTIONS_STEP_DEBUG = true
  • ACTIONS_RUNNER_DEBUG = true

Debug dentro do Workflow

- name: Debug info
  run: |
    echo "Event: ${{ github.event_name }}"
    echo "Ref: ${{ github.ref }}"
    echo "SHA: ${{ github.sha }}"
    echo "Actor: ${{ github.actor }}"
    echo "Repository: ${{ github.repository }}"
    env

- name: Debug context
  run: echo '${{ toJSON(github) }}'

Testar Localmente (act)

# Instalar act
brew install act

# Executar localmente
act push

# Executar job específico
act -j test

Referência: nektos/act

Erros Comuns e Antipadrões

1. Hardcoding de Secrets

# Mau exemplo
- run: curl -H "Authorization: Bearer abc123" https://api.example.com

# Bom exemplo
- run: curl -H "Authorization: Bearer ${{ secrets.API_TOKEN }}" https://api.example.com

2. Configuração Inadequada de Cache Key

# Mau exemplo: Key fixa, cache não atualiza
key: my-cache

# Bom exemplo: Incluir hash de arquivo de dependência
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

3. Loop Infinito

# Mau exemplo: Trigger pelo próprio commit
on:
  push:
    branches: [main]

jobs:
  commit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: |
          echo "update" >> file.txt
          git add .
          git commit -m "Auto update"
          git push

Resumo

Ao usar GitHub Actions, você obtém os seguintes benefícios:

  • Verificação automática de qualidade de código
  • Automação de deploy previne erros humanos
  • Compartilhamento de workflows consistentes em toda a equipe
  • Excelente experiência de desenvolvimento integrada ao GitHub

Comece com automação de testes simples e expanda gradualmente até o deploy.

Documentação Oficial

Melhores Práticas e Artigos

Ferramentas e Recursos

Livros

  • “Continuous Delivery” (Jez Humble, David Farley) - Livro-texto de CD/CD
  • “The DevOps Handbook” - Guia abrangente de DevOps
← Voltar para a lista