Deploy automático na VPS com GitHub Actions

    Deploys manuais via SSH são lentos, propensos a erro e não escalam. Com GitHub Actions, cada push no branch main dispara automaticamente o build, os testes e o deploy na VPS — tudo em minutos, sem intervenção humana. O workflow usa SSH com chave dedicada, rsync para sincronizar arquivos e docker compose para subir a nova versão.

    Configurar chave SSH dedicada para o CI/CD

    Nunca use sua chave pessoal no CI/CD — crie uma chave dedicada com acesso mínimo:

    bash
    # 1. Gerar chave SSH dedicada para o CI/CD (sem passphrase)
    ssh-keygen -t ed25519 -C "github-actions-deploy" -f ~/.ssh/id_ed25519_github_actions -N ""
    
    # 2. Autorizar a chave na VPS
    ssh-copy-id -i ~/.ssh/id_ed25519_github_actions.pub deploy@IP_DA_VPS
    
    # Ou manualmente na VPS:
    cat ~/.ssh/id_ed25519_github_actions.pub >> ~/.ssh/authorized_keys
    
    # 3. Adicionar ao GitHub Secrets (Settings → Secrets and variables → Actions):
    # SSH_PRIVATE_KEY   → conteúdo de ~/.ssh/id_ed25519_github_actions
    # SSH_HOST          → IP ou domínio da VPS
    # SSH_USER          → deploy
    # SSH_PORT          → 22 (ou porta customizada)
    
    # Verificar o conteúdo da chave privada:
    cat ~/.ssh/id_ed25519_github_actions
    Dica
    A chave SSH para CI/CD deve ser sem passphrase (para automação). Por isso, crie uma chave exclusiva para esse fim e restrinja o que o usuário deploy pode fazer na VPS (sem sudo para comandos críticos).

    Workflow completo de deploy

    Arquivo .github/workflows/deploy.yml que faz build, testes e deploy na VPS:

    yaml
    # .github/workflows/deploy.yml
    name: Deploy para VPS
    
    on:
      push:
        branches: [main]
      workflow_dispatch:          # permitir disparo manual
    
    jobs:
      deploy:
        runs-on: ubuntu-latest
        environment: production   # requer aprovação manual (opcional)
    
        steps:
          - name: Checkout código
            uses: actions/checkout@v4
    
          - name: Setup Node.js
            uses: actions/setup-node@v4
            with:
              node-version: '22'
              cache: 'npm'
    
          - name: Instalar dependências e build
            run: |
              npm ci
              npm run build
    
          - name: Rodar testes
            run: npm test
    
          - name: Configurar SSH
            uses: webfactory/ssh-agent@v0.9.0
            with:
              ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }}
    
          - name: Adicionar VPS ao known_hosts
            run: |
              ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
    
          - name: Sincronizar arquivos com rsync
            run: |
              rsync -avz --delete \
                --exclude='.git' \
                --exclude='node_modules' \
                --exclude='.env' \
                ./ ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }}:/opt/minha-api/
    
          - name: Deploy na VPS
            run: |
              ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "
                cd /opt/minha-api
                docker compose pull
                docker compose build app
                docker compose up -d --no-deps app
                docker compose ps
              "

    Health check pós-deploy

    Verifique que a nova versão está saudável antes de considerar o deploy bem-sucedido:

    yaml
          - name: Health check pós-deploy
            run: |
              echo "Aguardando aplicação iniciar..."
              sleep 10
    
              MAX_RETRIES=12
              RETRY=0
              until curl -sf https://api.seudominio.com.br/health; do
                RETRY=$((RETRY + 1))
                if [ $RETRY -ge $MAX_RETRIES ]; then
                  echo "Health check falhou após $MAX_RETRIES tentativas"
                  exit 1
                fi
                echo "Tentativa $RETRY/$MAX_RETRIES — aguardando..."
                sleep 5
              done
    
              echo "Deploy concluído com sucesso!"
    
          - name: Rollback em caso de falha
            if: failure()
            run: |
              ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "
                cd /opt/minha-api
                docker compose up -d --no-deps app --scale app=0
                docker compose up -d --no-deps app
              "
              echo "Rollback executado"

    Deploy sem downtime com docker compose

    Use --no-deps para reiniciar apenas o container da aplicação sem derrubar banco e nginx:

    bash
    # Na VPS — script de deploy chamado pelo GitHub Actions:
    # /opt/scripts/deploy.sh
    
    #!/bin/bash
    set -euo pipefail
    
    APP_DIR="/opt/minha-api"
    cd "$APP_DIR"
    
    echo "Iniciando deploy: $(date)"
    
    # Pull da nova imagem (se usar imagem do registry)
    docker compose pull app 2>/dev/null || true
    
    # Rebuild da imagem local
    docker compose build app
    
    # Restart apenas do container app (banco e nginx ficam up)
    docker compose up -d --no-deps --remove-orphans app
    
    # Aguardar container ficar healthy
    echo "Aguardando health check..."
    TIMEOUT=60
    ELAPSED=0
    until docker compose ps app | grep -q "healthy"; do
      sleep 2
      ELAPSED=$((ELAPSED + 2))
      if [ $ELAPSED -ge $TIMEOUT ]; then
        echo "Container não ficou healthy em ${TIMEOUT}s"
        exit 1
      fi
    done
    
    echo "Deploy concluído: $(date)"

    Notificar resultado do deploy no Telegram

    Envie notificação de sucesso ou falha ao final de cada deploy:

    yaml
          - name: Notificar sucesso no Telegram
            if: success()
            run: |
              curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
                -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
                -d text="✅ Deploy concluído
    Repo: ${{ github.repository }}
    Branch: ${{ github.ref_name }}
    Commit: ${{ github.sha }}
    Por: ${{ github.actor }}"
    
          - name: Notificar falha no Telegram
            if: failure()
            run: |
              curl -s -X POST "https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage" \
                -d chat_id="${{ secrets.TELEGRAM_CHAT_ID }}" \
                -d text="❌ Deploy FALHOU
    Repo: ${{ github.repository }}
    Branch: ${{ github.ref_name }}
    Ver: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"

    $ 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