Toda vez que faço push, meu código é testado, buildado e deployado automaticamente. Zero intervenção manual. Se você ainda faz deploy manualmente, este tutorial é para você.
Neste artigo, vou mostrar como montar um pipeline CI/CD completo usando GitHub Actions — desde o workflow YAML mais básico até um deploy automatizado em produção. Vamos usar um projeto Node.js como exemplo, mas os conceitos se aplicam a qualquer stack.
O que é CI/CD — explicação simples
CI (Continuous Integration) significa que toda vez que alguém faz push ou abre um pull request, o código é automaticamente testado. Se os testes passam, o código está íntegro. Se falham, você descobre imediatamente — não três semanas depois quando alguém faz merge de um branch quebrado.
CD (Continuous Deployment) vai além: depois que os testes passam, o código é automaticamente deployado em produção. Sem precisar de alguém logando no servidor, rodando scripts manuais ou rezando para nada quebrar.
A combinação dos dois cria um ciclo onde cada commit validado chega ao usuário final em minutos, não dias.
Por que GitHub Actions
Existem dezenas de ferramentas de CI/CD (Jenkins, GitLab CI, CircleCI, Travis CI…), mas GitHub Actions tem vantagens claras:
- Integrado ao GitHub: não precisa configurar serviço externo, webhooks ou integrações. Está tudo no mesmo lugar onde seu código vive.
- Free tier generoso: 2.000 minutos/mês para repositórios públicos e 500 minutos/mês para privados. Para a maioria dos projetos, você nunca paga nada.
- Marketplace com milhares de actions reutilizáveis: precisa fazer deploy na AWS? Há uma action. Precisa enviar notificação no Slack? Há uma action. A comunidade já resolveu a maioria dos problemas comuns.
- YAML versionado com seu código: o pipeline é um arquivo no repositório. Muda junto com o código, passa por code review e tem histórico no Git.
Anatomia de um workflow
Todo workflow fica em .github/workflows/ na raiz do repositório. É um arquivo YAML com esta estrutura:
name: Nome do Workflow # Nome exibido na aba Actions
on: [push] # Quando executar (trigger)
jobs: # Lista de jobs
meu-job: # Identificador do job
runs-on: ubuntu-latest # Máquina virtual onde roda
steps: # Passos sequenciais
- uses: actions/checkout@v4 # Action reutilizável
- run: echo "Hello World" # Comando shell
Triggers — quando o workflow executa
O campo on define quando o pipeline roda:
push— a cada push em branches específicospull_request— quando um PR é aberto ou atualizadoschedule— cron job (ex: rodar todo dia às 7h)workflow_dispatch— botão manual na interface do GitHub
Jobs e Steps
Um workflow pode ter múltiplos jobs que rodam em paralelo (por padrão). Cada job contém steps que rodam sequencialmente. Se um step falha, os seguintes não executam.
Runners
O runs-on define a máquina virtual:
ubuntu-latest— Linux (mais comum, mais rápido)windows-latest— Windows Servermacos-latest— macOS (útil para apps iOS/macOS)
Tutorial: Pipeline completo para um app Node.js
Vamos construir um pipeline incrementalmente — do teste básico ao deploy em produção.
Step 1: Workflow de testes
Crie o arquivo .github/workflows/ci.yml:
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm test
O que cada step faz:
- checkout — clona o repositório na VM
- setup-node — instala Node.js 22 e configura cache do npm
- npm ci — instala dependências (mais rápido e determinístico que
npm install) - npm test — roda os testes
Faça commit e push. Vá na aba Actions do seu repositório — o workflow já vai estar rodando.
Step 2: Adicionar build
Depois dos testes passarem, queremos gerar o build de produção:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm test
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
O upload-artifact salva os arquivos gerados para uso em jobs posteriores ou para download manual.
Step 3: Deploy automatizado
Vamos deployar no Cloudflare Pages (que é o que o Decodifica.Tech usa). Adicionamos um job separado que depende do job de teste:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci
- run: npm test
- run: npm run build
deploy:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- run: npm ci && npm run build
- uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
command: pages deploy dist/ --project-name=meu-projeto
Pontos importantes:
needs: test— o deploy só roda se o jobtestpassarif: github.ref == 'refs/heads/main'— deploy apenas no branch main (PRs não deployam)secrets.XXX— credenciais armazenadas de forma segura
Step 4: Configurar secrets
Secrets são variáveis criptografadas que o workflow usa sem expor no log. Para configurar:
- Vá em Settings → Secrets and variables → Actions
- Clique em New repository secret
- Adicione nome (ex:
CLOUDFLARE_API_TOKEN) e valor - No workflow, referencie com
${{ secrets.CLOUDFLARE_API_TOKEN }}
Secrets nunca aparecem nos logs — o GitHub automaticamente mascara qualquer tentativa de print.
Padrões avançados
Matrix builds — testar em múltiplas versões
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'npm'
- run: npm ci
- run: npm test
Isso cria dois jobs em paralelo — um com Node 20 e outro com Node 22. Se o app funciona nas duas versões, você tem confiança de que não vai quebrar para ninguém.
Cache de dependências
O actions/setup-node com cache: 'npm' já cuida do cache automaticamente. Para projetos que precisam de cache customizado:
- uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-
O cache reduz drasticamente o tempo de instalação de dependências — de minutos para segundos.
Concurrency groups — evitar deploys paralelos
Se dois pushes acontecem em sequência rápida, você não quer dois deploys rodando ao mesmo tempo:
concurrency:
group: deploy-production
cancel-in-progress: true
Isso garante que apenas o deploy mais recente execute. O anterior é cancelado automaticamente.
workflow_dispatch — trigger manual
Às vezes você quer rodar o pipeline manualmente (rollback, redeploy sem mudança de código):
on:
push:
branches: [main]
workflow_dispatch:
inputs:
environment:
description: 'Ambiente de deploy'
required: true
default: 'production'
type: choice
options:
- production
- staging
Isso adiciona um botão “Run workflow” na aba Actions com um dropdown para escolher o ambiente.
Exemplo real: pipeline do Decodifica.Tech
O pipeline do Decodifica.Tech roda inteiramente no GitHub Actions. Todos os dias, um cron schedule dispara às 07:00 BRT e em cerca de 4 minutos:
- Coleta as notícias do dia (Python)
- Gera o conteúdo com IA
- Faz o build com Hugo
- Deploya no Cloudflare Pages
Tudo automático. Zero intervenção humana. Se algo falha, eu recebo uma notificação e posso investigar — mas na imensa maioria das vezes, funciona silenciosamente enquanto eu durmo.
Armadilhas comuns
Cron schedules podem atrasar ou ser pulados
O GitHub não garante execução exata do cron. Em momentos de alta carga, seu schedule pode atrasar 5-15 minutos ou até ser pulado. Se o horário exato é crítico, considere um trigger externo que faz workflow_dispatch via API.
Secrets não estão disponíveis em forks
Quando alguém abre um PR de um fork, os secrets do seu repositório não são acessíveis no workflow desse PR. Isso é uma proteção de segurança — impede que alguém modifique o workflow para exfiltrar suas credenciais. O job de deploy simplesmente não vai funcionar em PRs de forks.
Sempre fixe versões das actions
Use actions/checkout@v4, não actions/checkout@main. A tag @main pode mudar a qualquer momento e quebrar seu pipeline. Versões fixas garantem reprodutibilidade.
# ✅ Correto — versão fixada
- uses: actions/checkout@v4
# ❌ Arriscado — pode mudar sem aviso
- uses: actions/checkout@main
Para segurança máxima, você pode fixar pelo SHA do commit:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11
Conclusão
GitHub Actions transformou a forma como faço deploy. E o melhor: é gratuito para a maioria dos projetos. Se quer ver um exemplo real completo, o workflow do Decodifica.Tech está aberto.
Comece simples — um workflow de testes. Depois adicione build. Depois deploy. Em uma tarde, você tem um pipeline que vai rodar sozinho pelos próximos meses sem precisar de manutenção.
A melhor parte? Uma vez que você configura CI/CD, nunca mais volta atrás. Deploy manual passa a parecer tão arcaico quanto copiar arquivos via FTP.
💬 Comentários