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:

    bash
    # 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:

    bash
    #!/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:

    yaml
    # .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:

    bash
    # 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-blue

    Histórico de deploys e auditoria

    Mantenha um registro dos deploys para facilitar investigação de incidentes:

    bash
    #!/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.

    Perguntas frequentes