CI/CD não precisa ser complexo para ser profissional. Um pipeline bem feito com GitHub Actions pode rodar lint, testes, build e deploy numa única configuração YAML que qualquer engenheiro do time consegue entender e manter.
Este artigo constrói um pipeline real do zero, explica cada decisão, e mostra os padrões que evitam os problemas mais comuns em produção.
Estrutura básica do workflow
Um workflow GitHub Actions é um arquivo YAML em .github/workflows/. A estrutura é simples: quando rodar, em qual ambiente, com quais passos.
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test --coverage
- run: pnpm build
Esse é o pipeline mínimo. Simples, mas já captura a maioria dos problemas antes de chegar em produção.
Cache de dependências: o detalhe que muda a velocidade
Sem cache, cada run instala todas as dependências do zero — pode ser 2–3 minutos só nisso. Com cache, a instalação de dependências que não mudaram é pulada.
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm' # ← hash do lockfile como chave de cache
Para npm, use cache: 'npm'. Para yarn, cache: 'yarn'. O cache usa o hash do lockfile como chave — quando o lockfile muda, o cache é invalidado automaticamente.
Separando CI de CD: o job de deploy
CI (integração contínua) roda em cada PR. CD (entrega contínua) roda apenas quando o código entra em produção. Separe em jobs distintos com dependência explícita:
jobs:
ci:
runs-on: ubuntu-latest
steps:
# ... lint, test, build
deploy:
needs: ci # só roda se CI passar
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' # só na main
environment: production # requer aprovação manual se configurado
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: |
# seu script de deploy aqui
Secrets: como fazer certo
Nunca coloque credenciais no YAML. Use GitHub Secrets — disponíveis em Settings > Secrets and variables > Actions do repositório.
- name: Deploy
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: ./scripts/deploy.sh
Para ambientes (production, staging), use Environment Secrets — permitem configurar secrets diferentes por ambiente e adicionar revisores obrigatórios antes do deploy.
Matrix builds: testando em múltiplas versões
Para bibliotecas ou APIs com suporte a múltiplas versões do Node, matrix builds rodam o CI em paralelo em todas as combinações:
jobs:
ci:
strategy:
matrix:
node-version: [18, 20, 22]
runs-on: ubuntu-latest
name: Test on Node ${{ matrix.node-version }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
Artifacts: preservando outputs entre jobs
Se você quer usar o resultado do build no job de deploy, ou preservar relatórios de cobertura, use artifacts:
- name: Upload coverage report
uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
- name: Upload build
uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7
O pipeline completo
name: CI/CD
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- run: pnpm lint
- run: pnpm typecheck
- run: pnpm test --coverage
- run: pnpm build
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
deploy:
needs: ci
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
environment: production
steps:
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- name: Deploy
env:
DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
run: ./scripts/deploy.sh
88 linhas. Pipeline completo, seguro, com cache, separação CI/CD, e deploy condicional. Isso é tudo que a maioria dos projetos precisa.