GitOps na VPS: deploy automático ao push
GitOps é a prática de usar o Git como fonte única de verdade para o estado da infraestrutura — qualquer mudança na aplicação é feita via commit, e a infraestrutura se sincroniza automaticamente. Na VPS, a implementação mais simples usa Watchtower (atualização automática de imagens Docker) ou um webhook que dispara o deploy no servidor sem que o CI precise de acesso SSH.
Watchtower: atualização automática de containers
Watchtower monitora o registry e atualiza containers automaticamente quando uma nova imagem é publicada:
# docker-compose.yml — adicionar Watchtower
services:
app:
image: ghcr.io/usuario/minha-api:main
restart: always
labels:
- "com.centurylinklabs.watchtower.enable=true" # monitorar este container
watchtower:
image: containrrr/watchtower
restart: always
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ~/.docker/config.json:/config.json:ro # credenciais do registry
environment:
WATCHTOWER_POLL_INTERVAL: 60 # verificar a cada 60 segundos
WATCHTOWER_CLEANUP: "true" # remover imagens antigas
WATCHTOWER_LABEL_ENABLE: "true" # monitorar apenas containers com label
WATCHTOWER_NOTIFICATIONS: "slack"
WATCHTOWER_NOTIFICATION_SLACK_HOOK_URL: ${SLACK_WEBHOOK}
WATCHTOWER_NOTIFICATION_SLACK_IDENTIFIER: "watchtower-vps-prod"Webhook de deploy: CI notifica a VPS
Alternativa ao Watchtower: a VPS expõe um endpoint que o GitHub Actions chama após publicar a imagem:
# Na VPS — servidor de webhook simples com Python
# /opt/webhook/webhook.py
from http.server import HTTPServer, BaseHTTPRequestHandler
import subprocess
import hmac
import hashlib
import os
SECRET = os.environ["WEBHOOK_SECRET"].encode()
class WebhookHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/deploy":
self.send_response(404)
self.end_headers()
return
# Verificar assinatura HMAC
content_length = int(self.headers.get("Content-Length", 0))
body = self.rfile.read(content_length)
signature = self.headers.get("X-Hub-Signature-256", "")
expected = "sha256=" + hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
self.send_response(403)
self.end_headers()
return
# Executar deploy
subprocess.Popen(["/opt/scripts/deploy.sh"])
self.send_response(200)
self.end_headers()
self.wfile.write(b"Deploy iniciado")
def log_message(self, *args):
pass # silenciar logs padrão
if __name__ == "__main__":
server = HTTPServer(("127.0.0.1", 9000), WebhookHandler)
server.serve_forever()Chamar webhook a partir do GitHub Actions
Após o build e push, o workflow notifica a VPS para puxar a nova imagem:
# .github/workflows/deploy.yml
- name: Notificar VPS para deploy
run: |
PAYLOAD='{"ref":"main","sha":"${{ github.sha }}"}'
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "${{ secrets.WEBHOOK_SECRET }}" | awk '{print "sha256="$2}')
curl -f -X POST https://seudominio.com.br/webhook/deploy \
-H "Content-Type: application/json" \
-H "X-Hub-Signature-256: $SIGNATURE" \
-d "$PAYLOAD"
# O Nginx roteia /webhook/ para localhost:9000 (webhook server)
# nginx.conf:
# location /webhook/ {
# allow all;
# proxy_pass http://127.0.0.1:9000;
# }deploy.sh chamado pelo webhook
Script de deploy na VPS que puxa a nova imagem e reinicia o container:
#!/bin/bash
# /opt/scripts/deploy.sh
set -euo pipefail
LOG="/var/log/deploy.log"
exec >> "$LOG" 2>&1
echo "=== Deploy: $(date) ==="
# Autenticar no registry (se imagem privada)
echo "$GITHUB_TOKEN" | docker login ghcr.io -u "$GITHUB_USER" --password-stdin 2>/dev/null
cd /opt/minha-api
# Puxar nova imagem
docker compose pull app
# Reiniciar apenas o container app
docker compose up -d --no-deps app
# Health check
sleep 5
if curl -sf http://localhost:3000/health > /dev/null; then
echo "Deploy bem-sucedido"
else
echo "Health check falhou — rollback"
docker compose up -d --no-deps --scale app=0 app
exit 1
fiComparação: Push vs Pull GitOps
Duas abordagens de GitOps com tradeoffs distintos:
# Push GitOps (mais comum em VPS):
# CI/CD faz SSH na VPS e executa o deploy diretamente
# + Simples de implementar
# + Feedback imediato no pipeline
# - CI precisa de credenciais SSH da VPS
# - Gargalo de segurança se CI for comprometido
# Pull GitOps (padrão em Kubernetes com ArgoCD/Flux):
# A VPS monitora o repositório/registry e atualiza sozinha
# + Sem credenciais de deploy no CI
# + VPS nunca expõe acesso de entrada para deploy
# - Lag de alguns segundos/minutos até sincronizar
# - Mais complexo de debugar
# Para VPS simples: Push GitOps (GitHub Actions via SSH)
# Para múltiplas VPS ou containers: Pull GitOps (Watchtower)
# Para Kubernetes: ArgoCD ou Flux (Pull GitOps nativo)$ 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.