CI/CD com Docker: build, push e deploy automatizado
Fazer build, push e deploy na mão funciona uma vez — um pipeline garante que funciona todas as vezes, do mesmo jeito.
Na lição de deploy da trilha de fundamentos, os comandos docker build, docker push e docker pull foram digitados na mão. Isso ensina o que cada passo faz, mas não escala: alguém esquece um passo, roda numa ordem errada, ou builda com o código errado. Um pipeline de CI/CD automatiza exatamente essa sequência, sempre do mesmo jeito, disparado por um evento (um push, um merge de PR).
O pipeline: build → teste → push → deploy
name: Deploy
on:
push:
branches: [main]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build da imagem
run: docker build -t minha-api:${{ github.sha }} .
- name: Rodar testes dentro do container
run: docker run --rm minha-api:${{ github.sha }} npm test
- name: Login no registry
run: echo "${{ secrets.REGISTRY_TOKEN }}" | docker login registry.exemplo.com -u ci --password-stdin
- name: Push da imagem
run: |
docker tag minha-api:${{ github.sha }} registry.exemplo.com/minha-api:${{ github.sha }}
docker tag minha-api:${{ github.sha }} registry.exemplo.com/minha-api:latest
docker push registry.exemplo.com/minha-api:${{ github.sha }}
docker push registry.exemplo.com/minha-api:latest
- name: Deploy no servidor
run: ssh deploy@servidor "docker compose pull && docker compose up -d"Rodar teste DENTRO do container, não fora dele
Repare que o passo de teste roda docker run --rm minha-api:sha npm test — dentro da própria imagem que acabou de ser buildada, não numa instalação separada de Node no runner do CI. Isso garante que o ambiente testado é exatamente o mesmo que vai pra produção: mesma versão de sistema operacional, mesmas bibliotecas nativas, mesma versão de runtime. Testar fora do container e depois buildar a imagem separadamente reabre a porta pro clássico "passou no CI, quebrou em produção".
Tag baseada no commit, não só 'latest'
Tagear com github.sha (o hash do commit) além de latest dá rastreabilidade: dá pra saber exatamente qual código gerou aquela imagem, e — se algo quebrar depois do deploy — fazer rollback pra uma tag anterior específica, em vez de "latest", que muda de significado a cada push.
# volta pra imagem do commit anterior, sem precisar rebuildar nada
docker pull registry.exemplo.com/minha-api:a1b2c3d
docker compose up -dRedeploy automatizado no servidor
O último passo do pipeline conecta no servidor (via SSH, ou um webhook que dispara um script equivalente) e roda docker compose pull (baixa a imagem nova do registry) seguido de docker compose up -d (recria os containers que mudaram, mantendo os que não mudaram intactos). O servidor nunca precisa ter o código-fonte nem as ferramentas de build — só o Docker instalado e acesso ao registry.
Exercício
O pipeline acima builda, testa, publica e faz deploy sempre que há um push na branch main. O time quer que pull requests também rodem o build e os testes (pra pegar erro antes do merge), mas sem nunca publicar imagem nem fazer deploy a partir de um PR. O que muda no on: e nos jobs desse workflow?