Estratégia de versionamento para rollback
O rollback funciona apenas quando você mantém a imagem anterior disponível:
# docker-compose.yml na VPS — usar tag de versão, não :latest
services:
app:
image: ghcr.io/usuario/minha-api:main # sempre a última do branch
# OU com versão semântica (mais controlado):
# image: ghcr.io/usuario/minha-api:1.2.3
# Estratégia de tags no GitHub Actions:
# ghcr.io/usuario/app:main — última build do main
# ghcr.io/usuario/app:sha-abc1234 — build específica (por commit)
# ghcr.io/usuario/app:1.2.3 — release semântico
# Para rollback: saber qual era a imagem anterior
# Na VPS — ver imagens disponíveis localmente:
docker images ghcr.io/usuario/minha-api
# Ver qual imagem está rodando atualmente:
docker inspect minha-api_app_1 | python3 -c "
import sys,json
info = json.load(sys.stdin)
print(info[0]['Config']['Image'])
"Script de deploy com rollback automático
Script que salva a versão atual, faz o deploy e reverte em caso de falha:
#!/bin/bash
# /opt/scripts/deploy-with-rollback.sh
set -euo pipefail
APP_DIR="/opt/minha-api"
HEALTH_URL="http://localhost:3000/health"
LOG="/var/log/deploy.log"
exec >> "$LOG" 2>&1
echo "=== Deploy iniciado: $(date) ==="
cd "$APP_DIR"
# 1. Salvar versão atual para rollback
CURRENT_IMAGE=$(docker compose images app -q 2>/dev/null || echo "none")
echo "Versão atual: $CURRENT_IMAGE"
# Função de rollback
rollback() {
echo "ROLLBACK: revertendo para $CURRENT_IMAGE"
if [ "$CURRENT_IMAGE" != "none" ]; then
sed -i "s|image:.*|image: $CURRENT_IMAGE|" docker-compose.yml
docker compose up -d --no-deps app
echo "Rollback concluído"
else
echo "Sem versão anterior para rollback"
fi
exit 1
}
# 2. Puxar nova imagem
echo "Puxando nova imagem..."
docker compose pull app || rollback
# 3. Fazer deploy
echo "Iniciando novo container..."
docker compose up -d --no-deps app || rollback
# 4. Health check com retry
echo "Verificando saúde..."
MAX_RETRIES=12
RETRY=0
until curl -sf "$HEALTH_URL" > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRIES ]; then
echo "Health check falhou após ${MAX_RETRIES} tentativas"
rollback
fi
echo "Tentativa $RETRY/$MAX_RETRIES..."
sleep 5
done
echo "=== Deploy concluído com sucesso: $(date) ==="Rollback no GitHub Actions workflow
Integre o rollback diretamente no workflow do GitHub Actions:
# .github/workflows/deploy.yml
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build e push imagem
id: build
uses: docker/build-push-action@v6
with:
push: true
tags: |
ghcr.io/usuario/app:main
ghcr.io/usuario/app:sha-${{ github.sha }}
- name: Configurar SSH
uses: webfactory/ssh-agent@v0.9.0
with:
ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Salvar versão atual na VPS
id: save-current
run: |
CURRENT=$(ssh deploy@${{ secrets.SSH_HOST }} \
"docker inspect minha-api_app_1 --format='{{.Config.Image}}' 2>/dev/null || echo 'none'")
echo "current_image=$CURRENT" >> $GITHUB_OUTPUT
- name: Deploy
id: deploy
run: |
ssh deploy@${{ secrets.SSH_HOST }} "/opt/scripts/deploy-with-rollback.sh"
- name: Rollback em caso de falha
if: failure() && steps.deploy.outcome == 'failure'
run: |
ssh deploy@${{ secrets.SSH_HOST }} "
docker pull ${{ steps.save-current.outputs.current_image }}
cd /opt/minha-api
IMAGE=${{ steps.save-current.outputs.current_image }} docker compose up -d --no-deps app
"Blue/Green deployment para zero downtime
Deploy blue/green mantém duas versões rodando e troca o tráfego após validação:
# Conceito blue/green com Nginx + dois containers:
# 1. Container "blue" rodando na porta 3000 (versão atual)
# 2. Deploy "green" na porta 3001 (nova versão)
# 3. Testar a versão green
# 4. Atualizar Nginx para apontar para green (3001)
# 5. Se OK: parar blue. Se falhar: manter blue, parar green.
# docker-compose.yml:
services:
app-blue:
image: ghcr.io/usuario/app:current
ports: ["127.0.0.1:3000:3000"]
app-green:
image: ghcr.io/usuario/app:new
ports: ["127.0.0.1:3001:3000"]
profiles: ["green"] # sobe apenas quando solicitado
# Script de swap blue/green:
#!/bin/bash
# 1. Subir green
docker compose --profile green up -d
# 2. Health check no green
sleep 5
curl -sf http://localhost:3001/health || (docker compose --profile green down && exit 1)
# 3. Trocar Nginx para green
sed -i 's/proxy_pass http://localhost:3000/proxy_pass http://localhost:3001/' /etc/nginx/sites-available/app
nginx -s reload
# 4. Parar blue
docker compose stop app-blueHistórico de deploys e auditoria
Mantenha um registro dos deploys para facilitar investigação de incidentes:
#!/bin/bash
# Adicionar ao script de deploy:
DEPLOY_LOG="/var/log/deploys.jsonl"
log_deploy() {
local status="$1"
local image="$2"
local git_sha="$3"
echo "{
\"timestamp\": \"$(date -Iseconds)\",
\"status\": \"$status\",
\"image\": \"$image\",
\"git_sha\": \"$git_sha\",
\"user\": \"github-actions\",
\"host\": \"$(hostname)\"
}" >> "$DEPLOY_LOG"
}
# Chamar no script:
log_deploy "success" "ghcr.io/usuario/app:sha-abc1234" "abc1234"
log_deploy "rollback" "ghcr.io/usuario/app:sha-def5678" "def5678"
# Consultar histórico:
cat /var/log/deploys.jsonl | python3 -m json.tool | grep -A 5 "rollback"
# Últimos 10 deploys:
tail -10 /var/log/deploys.jsonl | python3 -c "
import sys,json
for line in sys.stdin:
d = json.loads(line)
print(f"{d['timestamp']} [{d['status'].upper()}] {d['image']}")
"