Todo workflow do GitHub Actions mora em .github/workflows/ e define três coisas: o gatilho, a máquina e a ordem dos passos. O YAML abaixo roda lint, typecheck, teste e build num projeto Node com pnpm.
# .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
Quatro passos cobrem o que costuma quebrar antes do merge: o lint pega formatação fora do padrão, o typecatch pega tipo fechado, o teste pega regressão e o build pega bundle quebrado. O restante do arquivo resolve time e infraestrutura.
Cache de dependências
Sem cache, cada execução instala todas as dependências do zero e o run perde de 2 a 3 minutos nisso. Você liga o cache no próprio setup-node, que usa o hash do lockfile como chave:
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm' # chave: hash do lockfile
Mexeu no lockfile, o runner refaz a instalação. Projeto em npm usa cache: 'npm' e projeto em yarn usa cache: 'yarn'. O guia de caching do GitHub Actions documenta a chave, o caminho e a restauração do cache.
CI e deploy em jobs separados
Verificação roda em todo pull request. Deploy roda quando o código chega na main. Dois jobs ligados por needs e limitados por if mantêm essa fronteira:
jobs:
ci:
runs-on: ubuntu-latest
steps:
# lint, typecheck, test, build
deploy:
needs: ci # depende do job de CI
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
environment: production # aprovação manual quando configurada
steps:
- uses: actions/checkout@v4
- name: Deploy to production
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
run: ./scripts/deploy.sh
environment: production amarra o deploy aos environments do repositório. Você define revisores obrigatórios ali, e o deploy espera alguém aprovar antes de rodar. O documento de environments cobre proteção por ambiente e proteção por branch.
Secrets fora do YAML
Credencial escrita no YAML entra no histórico do Git e em todo log de execução. Você guarda o valor em Settings > Secrets and variables > Actions e injeta só no passo que precisa dele:
- 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
Environment secrets separam valores por ambiente e herdam as aprovações do environment. O guia de secrets define o escopo de cada valor e o que acontece quando você imprime um secret num echo.
Matrix builds para várias versões do Node
Biblioteca que promete suporte a Node 18, 20 e 22 precisa provar isso a cada PR. A matriz executa o mesmo job em paralelo, uma execução por versão:
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 }}
O documento de matrix cobre fail-fast, que cancela as execuções restantes quando uma falha, e a lista de combinações excluídas.
Artifacts entre jobs
O deploy consome o dist/ que o job de CI gerou. Upload e download passam esse diretório adiante:
# no job ci
- uses: actions/upload-artifact@v4
with:
name: dist
path: dist/
retention-days: 7
# no job deploy
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
Relatório de cobertura usa o mesmo caminho: um upload com name: coverage-report preserva o HTML do run para quem quiser abrir depois.
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
O arquivo tem 88 linhas e cobre cache, separação entre verificação e deploy, artefatos entre jobs e proteção de environment. A maioria dos projetos fecha o pipeline aqui.
Referências
- Workflow syntax for GitHub Actions (GitHub Docs)
- Caching dependencies to speed up workflows (GitHub Docs)
- Using environments for deployment (GitHub Docs)
- Using secrets in GitHub Actions (GitHub Docs)
- Using a matrix for your jobs (GitHub Docs)
Curtiu o conteúdo?
Construo produtos web e soluções com IA do jeito certo — arquitetura sólida, código sustentável e entrega real.
Vamos conversar