Índice
- O que é CI/CD (em palavras simples)
- Por que um pipeline precisa "escalar"
- As etapas de um pipeline completo
- Build: a fundação do pipeline
- Testes: a rede de segurança
- Deploy automático: do commit à produção
- Rollback: o botão "desfazer" profissional
- Estratégias para times grandes
- Exemplo real com GitHub Actions
- Erros comuns
- Checklist final
O que é CI/CD (em palavras simples) {#o-que-e}
Antes de usar a sigla, vamos explicar o que ela significa. CI é Continuous Integration (Integração Contínua) e CD é Continuous Delivery (Entrega Contínua) ou Continuous Deployment (Deploy Contínuo), dependendo do nível de automação que você deseja.
Imagine uma cozinha de restaurante. Sem CI, cada cozinheiro prepara seu prato sozinho, no seu canto, e só no final tudo é juntado — frequentemente os temperos não combinam e ninguém percebeu até o cliente reclamar. Com CI, a cada passo todo mundo prova a receita juntos e ajusta na hora. Com CD, o prato sai da cozinha para a mesa do cliente automaticamente, sem precisar de um garçom intermediário que confere tudo de novo.
Em software: CI é integrar o código de todas as pessoas desenvolvedoras várias vezes ao dia em um branch compartilhado, rodando testes automaticamente a cada integração. CD é garantir que esse código integrado esteja sempre pronto para ir para produção — seja com um clique humano (Continuous Delivery) ou sozinho (Continuous Deployment).
Um pipeline é a sequência automática de etapas que executa tudo isso. Pense numa esteira de montagem de uma fábrica: a peça entra crus, passa por várias estações (solda, pintura, teste, embalagem) e sai pronta para entrega. Se qualquer estação detecta defeito, a peça é rejeitada e volta para correção.
Por que um pipeline precisa "escalar" {#por-que-escalar}
Um pipeline que funciona para 2 pessoas em um repositório quase sempre quebra com 50 pessoas em 20 repositórios. Os sintomas clássicos:
- Builds lentos: o pipeline demora 40 minutos, o time para de esperar e faz push direto na main (pulo do gato para o desastre).
- Gargalo de infraestrutura: só há 2 runners disponíveis, 10 PRs esperando na fila, ninguém revisa código porque está esperando CI.
- Falsos negativos: testes instáveis ("flaky tests") quebram aleatoriamente, o time aprende a ignorar o CI vermelho, e bugs reais passam despercebidos.
- Custo cloud explosivo: cada PR roda testes E2E completos em 10 ambientes, a fatura da AWS triplica sem ninguém perceber.
Um pipeline escalável é aquele que continua rápido, confiável e barato mesmo quando o time, o número de repositórios e a frequência de deploys crescem 5x, 10x, 50x. Isso não acontece por acidente — é projeto.
| Sintoma | Pipeline que NÃO escala | Pipeline que escala |
|---|---|---|
| Tempo de build | 30-60 min, cresce linearmente | 5-10 min, paralelizado |
| Confiabilidade | 80% verde, 20% flaky | 99%+ verde, flaky = bloqueio |
| Custo por PR | $5-15 | $0.50-2 |
| Fila em horário pico | 20+ min esperando runner | < 2 min |
| Onboarding de novo repo | 2 dias de config | 1 comando (template reutilizável) |
As etapas de um pipeline completo {#etapas}
Um pipeline moderno tem, no mínimo, estas estações:
[Commit/PR]
↓
1. Checkout & cache ← baixar código e dependências cacheadas
↓
2. Lint & format check ← estilo de código (rapidíssimo)
↓
3. Build ← compilar, empacotar, criar imagem Docker
↓
4. Testes unitários ← isolados, rápidos (< 1 min)
↓
5. Testes de integração ← sobem dependências (banco, Redis) em containers
↓
6. Security scan ← SAST, dependências vulneráveis, secrets leak
↓
7. Artefato + imagem ← publicar em registry
↓
8. Deploy (staging) ← ambiente de validação
↓
9. Testes E2E / smoke ← fluxo crítico em ambiente real
↓
10. Deploy (produção) ← com estratégia + rollback automático
Cada etapa deve ser paralelizável quando possível e cachear tudo que puder. A regra de ouro: o que muda pouco deve ser reaproveitado, o que muda muito deve rodar em paralelo.
Build: a fundação do pipeline {#build}
O build é onde o código-fonte vira algo executável. Em linguagens compiladas (Go, Rust, Java) é a compilação; em linguagens interpretadas (Python, Node) é a instalação de dependências + transpilação (TypeScript → JavaScript, por exemplo).
Dicas de build escalável:
- Cache de dependências: reusar
node_modules/,~/.m2,~/.cargoentre execuções economiza minutos. GitHub Actions temactions/cachenativo. - Build em container: garantir que o build rode igual na máquina de
qualquer pessoa e no CI. Um
Dockerfilede build padroniza o ambiente. - Docker layer caching: em imagens Docker, copie primeiro os arquivos de
dependência (
package.json,go.mod) e instale antes de copiar o código. Assim a layer de dependências fica em cache e só invalida quando muda. - Build matrix: testar em múltiplas versões (Node 18 e 20, Python 3.10 e 3.12) em paralelo, não em sequência.
# Dockerfile otimizado para cache de layers
FROM node:20-alpine AS builder
WORKDIR /app
# Copia só manifestos primeiro → layer de deps cacheada
COPY package*.json ./
RUN npm ci --omit=dev
# Só agora copia o código → só esta layer invalida a cada commit
COPY . .
RUN npm run build
# Imagem final enxuta (multi-stage)
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
Testes: a rede de segurança {#testes}
Sem testes, CI é só um empacotador caro. A pirâmide de testes clássica continua válida e é o que mantém o pipeline rápido:
▲
/E2E\ ← poucos, lentos, em ambiente real
/──────\
/Integração\ ← médios, sobem dependências
/────────────\
/ Unitários \ ← muitos, rápidos, isolados
/──────────────────\
Testes unitários
Rápidos (milissegundos), sem rede nem banco. Devem cobrir a maior parte da lógica de negócio. Se um teste unitário demora mais que 200ms, algo está errado — provavelmente ele não é unitário de verdade.
Testes de integração
Sobem dependências reais em containers (Testcontainers é a ferramenta padrão). Validam que seu código conversa certo com Postgres, Redis, Kafka. Demoram segundos, não milissegundos.
Testes E2E
Simulam um usuário real navegando (Playwright, Cypress). São lentos e frágeis, por isso devem ser poucos e focar nos fluxos críticos: login, checkout, pagamento. Rodar E2E em todo PR é o erro mais comum de custo de CI.
Lidando com testes flaky
Teste flaky é aquele que ora passa, ora falha, sem mudança de código. É o pior inimigo da confiança no pipeline. Estratégia:
- Detectar: marcar testes que falham e passam no retry como suspeitos.
- Quarentena: mover para uma suite separada que não bloqueia o PR.
- Corrigir ou remover: um teste flaky é pior que não ter teste, porque ensina o time a ignorar o CI vermelho.
Regra: um pipeline que pisca verde/vermelho é tratado como enfeite. Preferência absoluta: CI confiável, mesmo que mais lento, do que CI rápido e instável.
Deploy automático: do commit à produção {#deploy}
Deploy automatizado é onde o "CD" brilha. Mas automático não significa irresponsável — significa repetível e auditável.
Ambientes
- Dev: local da pessoa desenvolvedora.
- Staging: cópia fiel de produção (mesma versão de libs, dados anonimizados).
- Produção: onde o usuário real está.
Estratégias de deploy
| Estratégia | Como funciona | Quando usar |
|---|---|---|
| Rolling | Substitui instâncias antigas por novas aos poucos | Default, sem downtime |
| Blue/Green | Dois ambientes idênticos; switch de tráfego num botão | Rollback instantâneo |
| Canary | 1% → 5% → 25% → 100% do tráfego na nova versão | Risco alto de regressão |
| Feature flag | Código novo dorme em produção, liga por flag | Experimentação A/B |
Pipeline de deploy vs. deploy manual
O deploy manual depende de uma pessoa rodar kubectl apply ou clicar num painel.
É lento, propenso a erro e não escalável. O deploy via pipeline é determinístico:
as mesmas ações sempre na mesma ordem, com logs auditáveis e gates de qualidade.
# Trecho de deploy com gate de aprovação
- name: Deploy to staging
run: ./scripts/deploy.sh staging
- name: Smoke tests
run: ./scripts/smoke.sh staging-url
- name: Aguardar aprovação manual (produção)
uses: trstringer/manual-approval-gateway@v1
with:
approvers: tech-leads
minimum-approvals: 1
- name: Deploy to production (canary 10%)
run: ./scripts/deploy.sh production --canary 10
Rollback: o botão "desfazer" profissional {#rollback}
Todo deploy vai falhar um dia. A diferença entre um incidente de 5 minutos e um de 5 horas é ter rollback automático e testado.
Tipos de rollback
- Rollback de versão (imagem): voltar para a imagem Docker anterior. Rápido, resolve bugs de código.
- Rollback de schema (banco): mais delicado. Migrações destrutivas
(
DROP COLUMN) não têm volta trivial. Por isso, migrações devem ser reversíveis e feitas em duas fases (expand → contract). - Rollback de feature flag: desligar a flag. O mais barato — não precisa nem redeployar.
Automatizando o rollback
Defina métricas que disparam rollback sozinhas: taxa de erro > 1% por 2 minutos, latência P99 > 500ms, taxa de 5xx acima do baseline. Ferramentas como Argo Rollouts e Flagger fazem isso nativamente no Kubernetes.
# Argo Rollouts: análise automática que reverte se erro subir
analysis:
templates:
- templateName: success-rate
startingStep: 2 # só analisa depois de 10% de tráfego
metrics:
- name: success-rate
successCondition: result >= 0.99
failureLimit: 2 # 2 falhas consecutivas → rollback automático
Estratégias para times grandes {#times-grandes}
Quando o time passa de ~10 pessoas, surgem problemas novos. Aqui vão as estratégias que separam pipelines que escalam de pipelines que travam.
1. Reusable workflows
No GitHub Actions, você pode reutilizar workflows inteiros. Em vez de cada repositório copiar 200 linhas de YAML, chame um workflow central:
# .github/workflows/deploy.yml em qualquer repo
jobs:
deploy:
uses: org/inicialize-pipeline/.github/workflows/deploy-template.yml@v3
with:
service-name: ${{ github.repository }}
environment: production
secrets: inherit
Mudou a versão do template? Atualize @v3 para @v4 e todos os repositórios
herdam a melhoria num commit.
2. Monorepo vs. multi-repo
- Monorepo: um repositório, pipeline precisa ser inteligente para só rodar testes dos pacotes afetados (ferramentas: Nx, Turborepo, Bazel).
- Multi-repo: cada repo tem seu pipeline, mas compartilha templates reutilizáveis para não repetir configuração.
3. Cache agressivo
- Cache de build tools (npm, pip, cargo, gradle).
- Cache de Docker (actions/cache para
/var/lib/dockerou BuildKit). - Cache de testes (Playwright armazena screenshots e traces).
4. Self-hosted runners / runners sob demanda
Runners do GitHub são limitados e pagos por minuto. Para times grandes, runners auto-hospedados com auto-scaling (Actions Runner Controller no K8s) reduzem custo e fila. Para builds esporádicos pesados, use runners efêmeros subidos on-demand.
5. Gates de qualidade, não de egos
Bons gates: cobertura não pode cair, testes novos obrigatórios para novos arquivos, SAST sem vulnerabilidades críticas. Maus gates: "precisa de 3 approvals de staff engineers" — vira gargalo humano e não escala.
Exemplo real com GitHub Actions {#exemplo-github}
Aqui está um pipeline completo, comentado, pronto para adaptar:
# .github/workflows/ci-cd.yml
name: CI/CD
on:
push:
branches: [main]
pull_request:
# Cancela runs anteriores do mesmo PR — economiza runners
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
# Job 1: tudo rápido e paralelo
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint # ESLint, Prettier
test-unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm test -- --coverage # Vitest/Jest
# Job 2: build só roda se lint+test passarem
build:
needs: [lint, test-unit]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v5
with:
context: .
push: false
tags: app:${{ github.sha }}
cache-from: type=gha # cache de layers no GitHub
cache-to: type=gha,mode=max
- uses: actions/upload-artifact@v4
with: { name: image, path: image.tar }
# Job 3: deploy só na main, nunca em PR
deploy:
if: github.ref == 'refs/heads/main'
needs: build
environment: production
steps:
- uses: actions/download-artifact@v4
with: { name: image }
- run: ./scripts/deploy.sh ${{ github.sha }}
Pontos de atenção nesse exemplo:
concurrencycancela runs velhas do mesmo PR → economia de runner.needsdefine a ordem e falha rápida: se lint quebra, build nem roda.cache-from: type=ghareaproveita layers Docker entre execuções.environment: productioncria um gate de aprovação configurável no GitHub.- Deploy só na
main— PRs não tocam produção.
Erros comuns {#erros}
- Rodar tudo em série: cada job esperando o anterior terminar. Solução:
paralelize jobs independentes com
needsapenas onde há dependência real. - Ignorar cache: instalar dependências do zero a cada run. Cada
npm cisem cache custa 1-3 minutos que se somam em milhares de runs por mês. - Segredos no código: colocar chaves AWS direto no YAML. Use secrets do GitHub/OIDC e nunca logue tokens.
- Testes E2E em todo PR: lentidão e custo explosivo. Separe E2E para agendamento noturno ou apenas na main.
- Sem rollback testado: "vamos voltar manual se quebrar". No pânico do incidente, ninguém lembra os passos. Teste o rollback em staging periodicamente.
- Deploy direto na main sem gate: em times grandes, um push errado derruba produção. Use protection rules + revisão + checks obrigatórios.
- Migrações de banco no mesmo pipeline do app: quebra se a nova versão do app for rejeitada mas o schema já mudou. Separe migrações em pipeline próprio com deploy antes do app (padrão expand/contract).
- Achar que CI/CD é ferramenta, não cultura: comprar Jenkins/GitHub Actions não entrega valor se o time não segue a disciplina de "main sempre verde".
Checklist final {#checklist}
- Pipeline roda em menos de 10 minutos para 90% dos PRs
- Dependências cacheadas (build tools + Docker layers)
- Testes flaky em quarentena, não no caminho crítico
- Lint, unit, integração, security scan em paralelo
- Deploy com estratégia (rolling/blue-green/canary) e gate
- Rollback automático disparado por métricas (erro rate, latência)
- Migrações de banco separadas e reversíveis (expand/contract)
- Templates reutilizáveis para novos repositórios
- Runners com auto-scaling para evitar fila em horário pico
- Custo de CI monitorado e com alertas (orçamento mensal)
- main sempre deployable — protection rules + checks obrigatórios
- Runners e secrets auditados periodicamente
Quer um pipeline que escala com seu time? A Inicialize Tec implementa CI/CD do commit ao deploy, com rollback automático, observabilidade e templates reutilizáveis para múltiplos repositórios. Conversamos sobre seu caso.