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
| Ano | Ferramenta | Características |
|---|---|---|
| 2004 | Hudson/Jenkins | On-premise, baseado em plugins |
| 2011 | Travis CI | Cloud-based, integração GitHub |
| 2014 | CircleCI | Suporte Docker, builds rápidos |
| 2017 | GitLab CI | Integrado ao GitLab |
| 2019 | GitHub Actions | Integrado ao GitHub, marketplace |
Vantagens do GitHub Actions
- Integrado ao GitHub: Sem necessidade de serviço separado, experiência seamless
- Definição YAML: Infraestrutura como Código (IaC)
- Marketplace: Actions reutilizáveis abundantes
- Tier gratuito: Ilimitado para repositórios públicos
- 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étrica | Elite | Baixo Desempenho |
|---|---|---|
| Frequência de deploy | Múltiplas vezes/dia | Menos de 1x/mês |
| Lead time | Menos de 1 hora | Mais de 1 mês |
| Taxa de falha de mudança | 0-15% | 46-60% |
| Tempo de recuperação | Menos de 1 hora | Mais 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
| Conceito | Descrição | Exemplo |
|---|---|---|
| Workflow | Processo de automação definido em YAML | CI, deploy |
| Event | Trigger que inicia o workflow | push, pull_request, schedule |
| Job | Conjunto de steps executados no mesmo runner | test, build, deploy |
| Step | Tarefa individual | checkout, npm install |
| Action | Unidade de tarefa reutilizável | actions/checkout@v4 |
| Runner | Servidor que executa o workflow | ubuntu-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
| Comando | Uso | Características |
|---|---|---|
npm ci | Ambiente CI | Usa package-lock.json estritamente, rápido |
npm install | Ambiente de desenvolvimento | Prioriza package.json, pode atualizar lock |
Melhor prática: Sempre use
npm ciem 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
- Repositório GitHub → Settings → Secrets and variables → Actions
- Clique em “New repository secret”
- Digite nome (ex:
DEPLOY_TOKEN) e valor
Tipos de Secrets
| Tipo | Escopo | Uso |
|---|---|---|
| Repository secrets | Repositório único | Chaves API, tokens |
| Environment secrets | Apenas ambiente específico | Para produção/staging |
| Organization secrets | Todos os repositórios da org | Contas 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
- Princípio do menor privilégio: Conceder apenas permissões necessárias
- Rotação de secrets: Atualizar regularmente
- Prevenir exposição em logs: Secrets são automaticamente mascarados
- 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
| Alvo | Exemplo de Key | Restore 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=trueACTIONS_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.
Links de Referência
Documentação Oficial
- GitHub Actions Documentation - Referência oficial
- Workflow syntax - Sintaxe de workflow
- GitHub Actions Marketplace - Actions reutilizáveis
Melhores Práticas e Artigos
- Martin Fowler - Continuous Integration - Origem do CI
- DORA Metrics - Métricas de desempenho DevOps
- GitHub Actions Security Best Practices - Guia de segurança
Ferramentas e Recursos
- act - Executar GitHub Actions localmente
- actionlint - Análise estática de arquivos de workflow
- GitHub Actions Cheat Sheet - Cheat sheet oficial
Livros
- “Continuous Delivery” (Jez Humble, David Farley) - Livro-texto de CD/CD
- “The DevOps Handbook” - Guia abrangente de DevOps