Guia Completo de CI/CD com GitHub Actions - Construindo Pipelines de Automação

Intermediário | 75 min leitura | 2025.12.02

GitHub Actions é uma plataforma de CI/CD (Integração Contínua/Entrega Contínua) integrada ao GitHub. Você pode automatizar testes, builds e deploys com triggers de push ou pull request. Este artigo explica em detalhes como construir workflows práticos.

O que você aprenderá neste artigo

  1. Conceitos básicos do GitHub Actions
  2. Sintaxe de arquivos de workflow
  3. Automação de testes para projetos Node.js/Python
  4. Aceleração com cache
  5. Matrix build
  6. Deploy para produção
  7. Melhores práticas de segurança

Conceitos Básicos

Componentes do GitHub Actions

flowchart TB
    subgraph Workflow["Workflow<br/>.github/workflows/ci.yml"]
        subgraph TestJob["Job: test"]
            Step1["Step 1: actions/checkout@v4"]
            Step2["Step 2: actions/setup-node@v4"]
            Step3["Step 3: npm install"]
            Step4["Step 4: npm test"]
            Step1 --> Step2 --> Step3 --> Step4
        end

        subgraph DeployJob["Job: deploy"]
            DeploySteps["Deploy steps..."]
        end

        TestJob -->|depends on| DeployJob
    end
TermoDescrição
WorkflowProcesso de automação completo definido em YAML
JobUnidade de execução dentro do workflow. Executa no mesmo runner
StepTarefas individuais dentro do job. Actions ou comandos shell
ActionUnidade de tarefa reutilizável (actions/checkout, etc.)
RunnerServidor que executa o workflow

Primeiro Workflow

Estrutura de Arquivos

project/
└── .github/
    └── workflows/
        ├── ci.yml          # Para testes e build
        ├── deploy.yml      # Para deploy
        └── release.yml     # Para release

Workflow CI Básico

# .github/workflows/ci.yml
name: CI

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

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

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

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

Detalhes do Trigger (on)

Vários Triggers

on:
  # No push
  push:
    branches:
      - main
      - 'release/**'        # Pattern matching
    paths:
      - 'src/**'            # Apenas quando alterações em paths específicos
      - '!src/**/*.md'      # Excluir markdown
    tags:
      - 'v*'                # Quando criar tag

  # Pull request
  pull_request:
    branches: [main]
    types: [opened, synchronize, reopened]

  # Execução manual
  workflow_dispatch:
    inputs:
      environment:
        description: 'Deployment environment'
        required: true
        default: 'staging'
        type: choice
        options:
          - staging
          - production

  # Execução agendada (UTC)
  schedule:
    - cron: '0 0 * * 1'     # Segunda-feira 00:00 UTC

  # Chamada de outros workflows
  workflow_call:
    inputs:
      config-path:
        required: true
        type: string

Execução Condicional

jobs:
  deploy:
    # Executar apenas em push para branch main
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest
    steps:
      # ...

  notify:
    # Executar apenas se job anterior falhou
    if: failure()
    needs: [test, deploy]
    runs-on: ubuntu-latest
    steps:
      # ...

Variáveis de Ambiente e Secrets

Configurando Variáveis de Ambiente

# Nível de workflow
env:
  NODE_ENV: production
  CI: true

jobs:
  build:
    runs-on: ubuntu-latest
    # Nível de job
    env:
      DATABASE_URL: postgres://localhost/test

    steps:
      - name: Build with environment
        # Nível de step
        env:
          API_KEY: ${{ secrets.API_KEY }}
        run: |
          echo "Node env: $NODE_ENV"
          echo "Building..."

Usando Secrets

steps:
  - name: Deploy to production
    env:
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    run: |
      aws s3 sync ./dist s3://my-bucket

  # GITHUB_TOKEN está automaticamente disponível
  - name: Create release
    uses: actions/create-release@v1
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Environment Secrets

jobs:
  deploy-prod:
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com

    steps:
      # Pode acessar variáveis de ambiente e secrets de production
      - name: Deploy
        env:
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: ./deploy.sh

Estratégias de Cache

Cache npm

steps:
  - uses: actions/checkout@v4

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

  - run: npm ci

Cache Personalizado

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

  - name: Install dependencies
    if: steps.cache-npm.outputs.cache-hit != 'true'
    run: npm ci

  # Cache de artefatos de build
  - name: Cache build output
    uses: actions/cache@v4
    with:
      path: |
        .next/cache
        dist
      key: ${{ runner.os }}-build-${{ github.sha }}
      restore-keys: |
        ${{ runner.os }}-build-

Cache de Dependências (Múltiplas Linguagens)

steps:
  # Python
  - uses: actions/setup-python@v5
    with:
      python-version: '3.12'
      cache: 'pip'

  # Go
  - uses: actions/setup-go@v5
    with:
      go-version: '1.22'
      cache: true

  # Rust
  - uses: actions/cache@v4
    with:
      path: |
        ~/.cargo/bin/
        ~/.cargo/registry/index/
        ~/.cargo/registry/cache/
        target/
      key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}

Matrix Build

Testes em Múltiplas Versões

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [18, 20, 22]
        include:
          - node-version: 20
            is-primary: true
      fail-fast: false  # Continua outros se um falhar

    steps:
      - uses: actions/checkout@v4

      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - run: npm ci
      - run: npm test

      # Relatório de cobertura apenas na versão primária
      - name: Upload coverage
        if: matrix.is-primary
        uses: codecov/codecov-action@v4

Testes Cross-Platform

jobs:
  test:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
        node-version: [18, 20]
        exclude:
          # Pular combinação Windows + Node 18
          - os: windows-latest
            node-version: 18

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

    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm ci
      - run: npm test

Dependências Entre Jobs

Execução Sequencial

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

  test:
    needs: lint  # Executa após lint ter sucesso
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm test

  build:
    needs: [lint, test]  # Depende de múltiplos jobs
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci && npm run build

Passando Artefatos Entre Jobs

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

      - name: Upload build artifacts
        uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/
          retention-days: 1

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download build artifacts
        uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/

      - name: Deploy
        run: ./deploy.sh dist/

Exemplos Práticos de Workflow

CI/CD Completo para Projeto Node.js

# .github/workflows/ci-cd.yml
name: CI/CD

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

env:
  NODE_VERSION: '20'

jobs:
  # Verificação de qualidade de código
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - run: npm ci

      - name: Type check
        run: npm run type-check

      - name: Lint
        run: npm run lint

      - name: Format check
        run: npm run format:check

  # Testes
  test:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
          POSTGRES_DB: test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - run: npm ci

      - name: Run tests
        env:
          DATABASE_URL: postgres://test:test@localhost:5432/test
        run: npm run test:coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v4
        with:
          token: ${{ secrets.CODECOV_TOKEN }}

  # Build
  build:
    needs: [quality, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - run: npm ci
      - run: npm run build

      - uses: actions/upload-artifact@v4
        with:
          name: build
          path: dist/

  # Deploy para staging (no merge do PR)
  deploy-staging:
    needs: build
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    runs-on: ubuntu-latest
    environment:
      name: staging
      url: https://staging.myapp.com

    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build
          path: dist/

      - name: Deploy to staging
        env:
          DEPLOY_KEY: ${{ secrets.STAGING_DEPLOY_KEY }}
        run: |
          echo "Deploying to staging..."
          # Script de deploy

  # Deploy para produção (após aprovação manual)
  deploy-production:
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment:
      name: production
      url: https://myapp.com

    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build
          path: dist/

      - name: Deploy to production
        env:
          DEPLOY_KEY: ${{ secrets.PRODUCTION_DEPLOY_KEY }}
        run: |
          echo "Deploying to production..."
          # Script de deploy

Build e Push de Imagem Docker

name: Docker Build

on:
  push:
    tags: ['v*']

env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  build-and-push:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v4

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Extract metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=semver,pattern={{major}}.{{minor}}
            type=sha

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Workflows Reutilizáveis

Definindo 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 }}
          cache: 'npm'
          registry-url: 'https://registry.npmjs.org'

      - run: npm ci
        env:
          NODE_AUTH_TOKEN: ${{ secrets.npm-token }}

      - run: npm test

Chamando Workflow Reutilizável

# .github/workflows/ci.yml
name: CI

on: [push, pull_request]

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

Melhores Práticas de Segurança

Scan de Dependências

name: Security

on:
  push:
    branches: [main]
  schedule:
    - cron: '0 0 * * 1'  # Semanalmente

jobs:
  dependency-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Run npm audit
        run: npm audit --audit-level=high

      - name: Run Snyk scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

Tratamento Seguro de Secrets

steps:
  # Mau exemplo: Pode expor secret no log
  - name: Bad example
    run: echo ${{ secrets.API_KEY }}  # Pode aparecer no log

  # Bom exemplo: Passar como variável de ambiente
  - name: Good example
    env:
      API_KEY: ${{ secrets.API_KEY }}
    run: ./deploy.sh  # Usar $API_KEY no script

Princípio do Menor Privilégio

# Definir permissões mínimas no nível do workflow
permissions:
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    # Também pode sobrescrever no nível do job
    permissions:
      contents: read
    steps:
      # ...

Debug e Troubleshooting

Habilitando Logs de Debug

# Configurar nos Secrets do repositório
# ACTIONS_STEP_DEBUG: true
# ACTIONS_RUNNER_DEBUG: true

steps:
  - name: Debug info
    run: |
      echo "GitHub Context:"
      echo '${{ toJson(github) }}'
      echo "Runner Context:"
      echo '${{ toJson(runner) }}'

Re-execução Manual de Jobs

on:
  workflow_dispatch:
    inputs:
      debug_enabled:
        description: 'Run with debug logging'
        required: false
        default: 'false'

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Setup tmate session
        if: inputs.debug_enabled == 'true'
        uses: mxschmitt/action-tmate@v3

Resumo

Resumo dos pontos-chave de pipelines CI/CD com GitHub Actions.

Melhores Práticas de Design de Workflow

  1. Separação de jobs: Separar lint, test, build, deploy
  2. Uso de cache: Acelerar com cache de npm/pip, etc.
  3. Matrix build: Testes paralelos em múltiplos ambientes
  4. Separação de ambientes: Separar claramente staging/production

Segurança

  1. Princípio do menor privilégio: Definir permissions mínimas
  2. Gerenciamento de secrets: Usar Environment Secrets
  3. Scan de dependências: Verificação regular de vulnerabilidades
  4. Code scanning: Introduzir CodeQL

Pontos para Acelerar

  1. Configuração adequada de cache
  2. Maximizar execução paralela
  3. Remover steps desnecessários
  4. Usar imagens Docker slim

Com GitHub Actions, você pode melhorar significativamente a eficiência do processo de desenvolvimento.

← Voltar para a lista