Rollback automático de deploy
Um deploy com bug em produção precisa ser revertido em segundos, não em minutos. Rollback automático detecta falhas no health check logo após o deploy e reverte para a versão anterior sem intervenção humana. Combinando tags de imagem Docker versionadas com um script de rollback, você tem uma rede de segurança que cobre os cenários mais comuns.
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']}")
"$ runstack deploy --plan starter
Não quer configurar manualmente?
Não quer configurar manualmente? Implante o VPS em menos de 3 minutos com a Runstack. Infraestrutura da OPEN DATACENTER, com servidores no Brasil.