vps-setup

Pass

Setup completo de VPS Ubuntu/Debian para produção com Docker Swarm, Traefik v3 (SSL/HTTPS automático), Portainer CE e rede overlay. Baseado no SetupOrion v2.8.0. Executa todos os comandos automaticamente.

@openclaw
MIT2/22/2026
(0)
1.0k
3
3

Install Skill

Skills are third-party code from public GitHub repositories. SkillHub scans for known malicious patterns but cannot guarantee safety. Review the source code before installing.

Install globally (user-level):

npx skillhub install openclaw/skills/vps-setup

Install in current project:

npx skillhub install openclaw/skills/vps-setup --project

Suggested path: ~/.claude/skills/vps-setup/

SKILL.md Content

---
name: vps-setup
description: Setup completo de VPS Ubuntu/Debian para produção com Docker Swarm, Traefik v3 (SSL/HTTPS automático), Portainer CE e rede overlay. Baseado no SetupOrion v2.8.0. Executa todos os comandos automaticamente.
metadata:
  openclaw:
    requires:
      bins: []
    env:
      VPS_HOSTNAME: "Nome do servidor (ex: Orion)"
      VPS_NETWORK: "Nome da rede overlay Docker (ex: Impasevid)"
      VPS_EMAIL_SSL: "Email para certificados Let's Encrypt (ex: [email protected])"
      VPS_PORTAINER_DOMAIN: "Domínio do Portainer (ex: portainer.seudominio.com)"
      VPS_PORTAINER_USER: "Usuário admin do Portainer (ex: admin)"
      VPS_PORTAINER_PASS: "Senha admin do Portainer (mínimo 12 caracteres)"
      VPS_POSTGRES_PASS: "Senha do PostgreSQL (auto-gerada se vazia). Use openssl rand -hex 16"
      VPS_EVOLUTION_DOMAIN: "Domínio da Evolution API (ex: api.seudominio.com)"
      VPS_EVOLUTION_API_KEY: "Global API Key da Evolution (auto-gerada se vazia). Use openssl rand -hex 16"
---

# VPS Setup - Docker Swarm + Traefik + Portainer

Setup automatizado de VPS para produção. Instala Docker, inicia Swarm, cria rede overlay, deploya Traefik v3 com SSL automático e Portainer CE.

**Requisitos:** Ubuntu 20.04+ ou Debian 11+ com acesso root/sudo.

---

## Execução Completa (todos os passos em ordem)

Execute cada etapa na ordem. Todas as etapas são idempotentes (seguro re-executar).

---

### Etapa 1 - Atualizar Sistema e Configurar Servidor

```bash
# Atualizar sistema
sudo apt-get update && sudo apt-get upgrade -y

# Instalar utilitários essenciais
sudo apt-get install -y apt-utils apparmor-utils curl jq htpasswd ca-certificates gnupg lsb-release

# Configurar timezone
sudo timedatectl set-timezone America/Sao_Paulo

# Definir hostname do servidor
sudo hostnamectl set-hostname $VPS_HOSTNAME
sudo sed -i "s/127.0.0.1[[:space:]]localhost/127.0.0.1 $VPS_HOSTNAME/g" /etc/hosts
```

**Verificação:**
```bash
timedatectl | grep "Time zone"
# Esperado: America/Sao_Paulo

hostname
# Esperado: $VPS_HOSTNAME
```

---

### Etapa 2 - Instalar Docker

```bash
# Instalar Docker via script oficial
curl -fsSL https://get.docker.com | bash

# Habilitar e iniciar Docker
sudo systemctl enable docker
sudo systemctl start docker

# Fix: garantir compatibilidade com API mínima
sudo mkdir -p /etc/systemd/system/docker.service.d
sudo bash -c 'cat > /etc/systemd/system/docker.service.d/override.conf <<EOF
[Service]
Environment=DOCKER_MIN_API_VERSION=1.24
EOF'

sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl restart docker
```

**Se falhar**, instalar manualmente:
```bash
sudo install -m 0755 -d /etc/apt/keyrings
OS_ID=$(source /etc/os-release && echo "$ID")
OS_CODENAME=$(source /etc/os-release && echo "$VERSION_CODENAME")

curl -fsSL https://download.docker.com/linux/$OS_ID/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/$OS_ID $OS_CODENAME stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable docker
sudo systemctl start docker
```

**Verificação:**
```bash
docker --version
# Esperado: Docker version 2X.x.x

docker info --format '{{.Swarm.LocalNodeState}}'
# Esperado: inactive (ainda não iniciamos o Swarm)
```

---

### Etapa 3 - Iniciar Docker Swarm

```bash
# Obter IP público da VPS (ignora loopback e IPs internos)
IP=$(hostname -I | tr ' ' '\n' | grep -v '^127\.' | grep -v '^10\.0\.0\.' | head -n1)

# Iniciar Swarm
docker swarm init --advertise-addr $IP
```

**Verificação:**
```bash
docker info --format '{{.Swarm.LocalNodeState}}'
# Esperado: active

docker node ls
# Esperado: 1 node com STATUS=Ready, MANAGER STATUS=Leader
```

---

### Etapa 4 - Criar Rede Overlay e Volumes

```bash
# Criar rede overlay para comunicação entre containers
docker network create --driver=overlay $VPS_NETWORK

# Criar volumes necessários
docker volume create volume_swarm_shared
docker volume create volume_swarm_certificates
docker volume create portainer_data
docker volume create postgres_data
docker volume create evolution_instances
docker volume create evolution_redis
```

**Verificação:**
```bash
docker network ls | grep $VPS_NETWORK
# Esperado: linha com $VPS_NETWORK, DRIVER=overlay, SCOPE=swarm

docker volume ls | grep -E "shared|certificates|portainer|postgres|evolution"
# Esperado: 6 volumes listados
```

---

### Etapa 5 - Deploy do Traefik v3 (Proxy Reverso + SSL)

Criar o arquivo `traefik.yaml` e fazer deploy:

```bash
cat > /root/traefik.yaml << 'TRAEFIKEOF'
version: "3.7"
services:
  traefik:
    image: traefik:v3.5.3
    command:
      - "--api.dashboard=true"
      - "--providers.swarm=true"
      - "--providers.docker.endpoint=unix:///var/run/docker.sock"
      - "--providers.docker.exposedbydefault=false"
      - "--providers.docker.network=NETWORK_PLACEHOLDER"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.web.http.redirections.entryPoint.to=websecure"
      - "--entrypoints.web.http.redirections.entryPoint.scheme=https"
      - "--entrypoints.web.http.redirections.entrypoint.permanent=true"
      - "--entrypoints.websecure.address=:443"
      - "--entrypoints.web.transport.respondingTimeouts.idleTimeout=3600"
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.letsencryptresolver.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.letsencryptresolver.acme.storage=/etc/traefik/letsencrypt/acme.json"
      - "--certificatesresolvers.letsencryptresolver.acme.email=EMAIL_PLACEHOLDER"
      - "--log.level=DEBUG"
      - "--log.format=common"
      - "--log.filePath=/var/log/traefik/traefik.log"
      - "--accesslog=true"
      - "--accesslog.filepath=/var/log/traefik/access-log"

    volumes:
      - "vol_certificates:/etc/traefik/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

    networks:
      - network_overlay

    ports:
      - target: 80
        published: 80
        mode: host
      - target: 443
        published: 443
        mode: host

    deploy:
      placement:
        constraints:
          - node.role == manager
      labels:
        - "traefik.enable=true"
        - "traefik.http.middlewares.redirect-https.redirectscheme.scheme=https"
        - "traefik.http.middlewares.redirect-https.redirectscheme.permanent=true"
        - "traefik.http.routers.http-catchall.rule=Host(`{host:.+}`)"
        - "traefik.http.routers.http-catchall.entrypoints=web"
        - "traefik.http.routers.http-catchall.middlewares=redirect-https@docker"
        - "traefik.http.routers.http-catchall.priority=1"

volumes:
  vol_shared:
    external: true
    name: volume_swarm_shared
  vol_certificates:
    external: true
    name: volume_swarm_certificates

networks:
  network_overlay:
    external: true
    attachable: true
    name: NETWORK_PLACEHOLDER
TRAEFIKEOF

# Substituir placeholders pelas variáveis reais
sed -i "s/NETWORK_PLACEHOLDER/$VPS_NETWORK/g" /root/traefik.yaml
sed -i "s/EMAIL_PLACEHOLDER/$VPS_EMAIL_SSL/g" /root/traefik.yaml

# Deploy
docker stack deploy --prune --resolve-image always -c /root/traefik.yaml traefik
```

**Aguardar Traefik ficar online:**
```bash
# Verificar a cada 10s até o serviço mostrar 1/1
while ! docker service ls --filter name='traefik_traefik' --format '{{.Replicas}}' | grep -q '1/1'; do
  echo "Aguardando Traefik..."
  sleep 10
done
echo "Traefik online!"
```

**Verificação:**
```bash
docker service ls --filter name=traefik
# Esperado: traefik_traefik   1/1

curl -sI http://localhost:80 | head -3
# Esperado: HTTP redirect ou resposta do Traefik
```

---

### Etapa 6 - Deploy do Portainer CE (Gerenciador Docker)

Criar o arquivo `portainer.yaml` e fazer deploy:

```bash
cat > /root/portainer.yaml << 'PORTAINEREOF'
version: "3.7"
services:
  agent:
    image: portainer/agent:latest
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/volumes:/var/lib/docker/volumes
    networks:
      - network_overlay
    deploy:
      mode: global
      placement:
        constraints: [node.platform.os == linux]

  portainer:
    image: portainer/portainer-ce:latest
    command: -H tcp://tasks.agent:9001 --tlsskipverify
    volumes:
      - portainer_data:/data
    networks:
      - network_overlay
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]
      labels:
        - "traefik.enable=true"
        - "traefik.http.routers.portainer.rule=Host(`PORTAINER_DOMAIN_PLACEHOLDER`)"
        - "traefik.http.services.portainer.loadbalancer.server.port=9000"
        - "traefik.http.routers.portainer.tls.certresolver=letsencryptresolver"
        - "traefik.http.routers.portainer.service=portainer"
        - "traefik.docker.network=NETWORK_PLACEHOLDER"
        - "traefik.http.routers.portainer.entrypoints=websecure"
        - "traefik.http.routers.portainer.priority=1"

volumes:
  portainer_data:
    external: true
    name: portainer_data

networks:
  network_overlay:
    external: true
    attachable: true
    name: NETWORK_PLACEHOLDER
PORTAINEREOF

# Substituir placeholders
sed -i "s/NETWORK_PLACEHOLDER/$VPS_NETWORK/g" /root/portainer.yaml
sed -i "s/PORTAINER_DOMAIN_PLACEHOLDER/$VPS_PORTAINER_DOMAIN/g" /root/portainer.yaml

# Deploy
docker stack deploy --prune --resolve-image always -c /root/portainer.yaml portainer
```

**Aguardar Portainer ficar online:**
```bash
# Aguardar agent + portainer
while ! docker service ls --filter name='portainer_portainer' --format '{{.Replicas}}' | grep -q '1/1'; do
  echo "Aguardando Portainer..."
  sleep 10
done
echo "Portainer online!"
```

**Verificação:**
```bash
docker service ls --filter name=portainer
# Esperado: portainer_agent 1/1 (global), portainer_portainer 1/1
```

---

### Etapa 7 - Criar Conta Admin no Portainer

```bash
# Aguardar API do Portainer estar pronta (30s após container online)
sleep 30

# Criar usuário admin (tentativa com retry)
for i in 1 2 3 4; do
  RESPONSE=$(curl -k -s -X POST "https://$VPS_PORTAINER_DOMAIN/api/users/admin/init" \
    -H "Content-Type: application/json" \
    -d "{\"Username\": \"$VPS_PORTAINER_USER\", \"Password\": \"$VPS_PORTAINER_PASS\"}")
  
  if echo "$RESPONSE" | jq -e '.Id' > /dev/null 2>&1; then
    echo "Admin criado com sucesso!"
    break
  fi
  
  echo "Tentativa $i falhou, aguardando 15s..."
  sleep 15
done

# Obter token JWT para validar
TOKEN=$(curl -k -s -X POST "https://$VPS_PORTAINER_DOMAIN/api/auth" \
  -H "Content-Type: application/json" \
  -d "{\"username\":\"$VPS_PORTAINER_USER\",\"password\":\"$VPS_PORTAINER_PASS\"}" | jq -r .jwt)

echo "Token: $TOKEN"
```

**Verificação:**
```bash
curl -k -s "https://$VPS_PORTAINER_DOMAIN/api/status" | jq .
# Esperado: JSON com Version e InstanceID

echo "Portainer acessível em: https://$VPS_PORTAINER_DOMAIN"
echo "Usuário: $VPS_PORTAINER_USER"
```

---

### Etapa 8 - Deploy do PostgreSQL 14 (Banco de Dados)

O PostgreSQL é necessário para a Evolution API e outros serviços.

```bash
# Gerar senha do Postgres (ou usar VPS_POSTGRES_PASS se definida)
POSTGRES_PASS="${VPS_POSTGRES_PASS:-$(openssl rand -hex 16)}"
echo "Senha do PostgreSQL: $POSTGRES_PASS"

# Criar volume para dados do Postgres
docker volume create postgres_data

# Criar stack postgres.yaml
cat > /root/postgres.yaml << POSTGRESEOF
version: "3.7"
services:
  postgres:
    image: postgres:14
    command: >
      postgres
      -c max_connections=500
      -c shared_buffers=512MB
      -c timezone=America/Sao_Paulo

    volumes:
      - postgres_data:/var/lib/postgresql/data

    networks:
      - network_overlay

    environment:
      - POSTGRES_PASSWORD=${POSTGRES_PASS}
      - TZ=America/Sao_Paulo

    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          cpus: "1"
          memory: 1024M

volumes:
  postgres_data:
    external: true
    name: postgres_data

networks:
  network_overlay:
    external: true
    name: NETWORK_PLACEHOLDER
POSTGRESEOF

# Substituir placeholder da rede
sed -i "s/NETWORK_PLACEHOLDER/$VPS_NETWORK/g" /root/postgres.yaml

# Deploy
docker stack deploy --prune --resolve-image always -c /root/postgres.yaml postgres
```

**Aguardar PostgreSQL ficar online:**
```bash
while ! docker service ls --filter name='postgres_postgres' --format '{{.Replicas}}' | grep -q '1/1'; do
  echo "Aguardando PostgreSQL..."
  sleep 10
done
echo "PostgreSQL online!"
```

**Verificação:**
```bash
docker service ls --filter name=postgres
# Esperado: postgres_postgres 1/1

# Testar conexão
CONTAINER_ID=$(docker ps -q --filter "name=^postgres_postgres")
docker exec "$CONTAINER_ID" psql -U postgres -c "SELECT version();"
# Esperado: PostgreSQL 14.x

echo ""
echo "=== DADOS DO POSTGRESQL ==="
echo "Host: postgres (interno Docker)"
echo "Porta: 5432"
echo "Usuário: postgres"
echo "Senha: $POSTGRES_PASS"
```

---

### Etapa 9 - Deploy da Evolution API v2 (WhatsApp)

Instala a Evolution API com Redis para cache e PostgreSQL como banco de dados.

```bash
# Obter senha do Postgres (já deve estar definida na Etapa 8)
POSTGRES_PASS="${VPS_POSTGRES_PASS:-$(grep 'POSTGRES_PASSWORD' /root/postgres.yaml | awk -F '=' '{print $2}')}"

# Gerar Global API Key (ou usar VPS_EVOLUTION_API_KEY se definida)
EVOLUTION_API_KEY="${VPS_EVOLUTION_API_KEY:-$(openssl rand -hex 16)}"
echo "Evolution Global API Key: $EVOLUTION_API_KEY"

# Criar banco de dados para a Evolution no PostgreSQL
CONTAINER_ID=$(docker ps -q --filter "name=^postgres_postgres")
docker exec "$CONTAINER_ID" psql -U postgres -c "CREATE DATABASE evolution;" 2>/dev/null || echo "Banco 'evolution' já existe"

# Criar volumes
docker volume create evolution_instances
docker volume create evolution_redis

# Criar stack evolution.yaml
cat > /root/evolution.yaml << 'EVOLUTIONEOF'
version: "3.7"
services:

  evolution_api:
    image: evoapicloud/evolution-api:latest

    volumes:
      - evolution_instances:/evolution/instances

    networks:
      - network_overlay

    environment:
    ## Configuracoes Gerais
      - SERVER_URL=https://EVOLUTION_DOMAIN_PLACEHOLDER
      - AUTHENTICATION_API_KEY=EVOLUTION_APIKEY_PLACEHOLDER
      - AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true
      - DEL_INSTANCE=false
      - QRCODE_LIMIT=1902
      - LANGUAGE=pt-BR

    ## Configuracao do Cliente
      - CONFIG_SESSION_PHONE_CLIENT=OpenClaw
      - CONFIG_SESSION_PHONE_NAME=Chrome

    ## Banco de Dados
      - DATABASE_ENABLED=true
      - DATABASE_PROVIDER=postgresql
      - DATABASE_CONNECTION_URI=postgresql://postgres:POSTGRES_PASS_PLACEHOLDER@postgres:5432/evolution
      - DATABASE_CONNECTION_CLIENT_NAME=evolution
      - DATABASE_SAVE_DATA_INSTANCE=true
      - DATABASE_SAVE_DATA_NEW_MESSAGE=true
      - DATABASE_SAVE_MESSAGE_UPDATE=true
      - DATABASE_SAVE_DATA_CONTACTS=true
      - DATABASE_SAVE_DATA_CHATS=true
      - DATABASE_SAVE_DATA_LABELS=true
      - DATABASE_SAVE_DATA_HISTORIC=true

    ## Integracoes (chatbots)
      - N8N_ENABLED=true
      - EVOAI_ENABLED=true
      - OPENAI_ENABLED=true
      - DIFY_ENABLED=true
      - TYPEBOT_ENABLED=true
      - TYPEBOT_API_VERSION=latest

    ## Chatwoot
      - CHATWOOT_ENABLED=true
      - CHATWOOT_MESSAGE_READ=true
      - CHATWOOT_MESSAGE_DELETE=true
      - CHATWOOT_IMPORT_PLACEHOLDER_MEDIA_MESSAGE=false

    ## Cache Redis
      - CACHE_REDIS_ENABLED=true
      - CACHE_REDIS_URI=redis://evolution_redis:6379/1
      - CACHE_REDIS_PREFIX_KEY=evolution
      - CACHE_REDIS_SAVE_INSTANCES=false
      - CACHE_LOCAL_ENABLED=false

    ## S3 (desabilitado por padrao)
      - S3_ENABLED=false

    ## WhatsApp Business (Cloud API)
      - WA_BUSINESS_TOKEN_WEBHOOK=evolution
      - WA_BUSINESS_URL=https://graph.facebook.com
      - WA_BUSINESS_VERSION=v23.0
      - WA_BUSINESS_LANGUAGE=pt_BR

    ## Telemetria
      - TELEMETRY=false

    ## WebSocket
      - WEBSOCKET_ENABLED=false
      - WEBSOCKET_GLOBAL_EVENTS=false

    ## RabbitMQ (desabilitado por padrao)
      - RABBITMQ_ENABLED=false

    ## Webhook (desabilitado por padrao)
      - WEBHOOK_GLOBAL_ENABLED=false

    ## SQS (desabilitado por padrao)
      - SQS_ENABLED=false

    ## Provider
      - PROVIDER_ENABLED=false

    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints:
          - node.role == manager
      labels:
        - traefik.enable=true
        - traefik.http.routers.evolution.rule=Host(`EVOLUTION_DOMAIN_PLACEHOLDER`)
        - traefik.http.routers.evolution.entrypoints=websecure
        - traefik.http.routers.evolution.priority=1
        - traefik.http.routers.evolution.tls.certresolver=letsencryptresolver
        - traefik.http.routers.evolution.service=evolution
        - traefik.http.services.evolution.loadbalancer.server.port=8080
        - traefik.http.services.evolution.loadbalancer.passHostHeader=true

  evolution_redis:
    image: redis:latest
    command: ["redis-server", "--appendonly", "yes", "--port", "6379"]

    volumes:
      - evolution_redis:/data

    networks:
      - network_overlay

    deploy:
      placement:
        constraints:
          - node.role == manager
      resources:
        limits:
          cpus: "1"
          memory: 1024M

volumes:
  evolution_instances:
    external: true
    name: evolution_instances
  evolution_redis:
    external: true
    name: evolution_redis

networks:
  network_overlay:
    external: true
    name: NETWORK_PLACEHOLDER
EVOLUTIONEOF

# Substituir placeholders
sed -i "s/EVOLUTION_DOMAIN_PLACEHOLDER/$VPS_EVOLUTION_DOMAIN/g" /root/evolution.yaml
sed -i "s/EVOLUTION_APIKEY_PLACEHOLDER/$EVOLUTION_API_KEY/g" /root/evolution.yaml
sed -i "s/POSTGRES_PASS_PLACEHOLDER/$POSTGRES_PASS/g" /root/evolution.yaml
sed -i "s/NETWORK_PLACEHOLDER/$VPS_NETWORK/g" /root/evolution.yaml

# Deploy
docker stack deploy --prune --resolve-image always -c /root/evolution.yaml evolution
```

**Aguardar Evolution API ficar online:**
```bash
# Aguardar Redis + Evolution API
while ! docker service ls --filter name='evolution_evolution_redis' --format '{{.Replicas}}' | grep -q '1/1'; do
  echo "Aguardando Redis..."
  sleep 10
done
echo "Redis online!"

while ! docker service ls --filter name='evolution_evolution_api' --format '{{.Replicas}}' | grep -q '1/1'; do
  echo "Aguardando Evolution API..."
  sleep 10
done
echo "Evolution API online!"

# Aguardar inicialização completa
sleep 30
```

**Verificação:**
```bash
docker service ls --filter name=evolution
# Esperado: evolution_evolution_api 1/1, evolution_evolution_redis 1/1

# Testar API
curl -sk "https://$VPS_EVOLUTION_DOMAIN" | head -c 200
# Esperado: resposta JSON da API

echo ""
echo "=== DADOS DA EVOLUTION API ==="
echo "Manager: https://$VPS_EVOLUTION_DOMAIN/manager"
echo "BaseUrl: https://$VPS_EVOLUTION_DOMAIN"
echo "Global API Key: $EVOLUTION_API_KEY"
```

---

## Operações de Manutenção

### Reiniciar Traefik
```bash
docker service update --force $(docker service ls --filter name='traefik_traefik' -q)
```

### Reiniciar Portainer
```bash
docker service update --force $(docker service ls --filter name='portainer_agent' -q)
docker service update --force $(docker service ls --filter name='portainer_portainer' -q)
```

### Atualizar Portainer
```bash
docker stack deploy --prune --resolve-image always -c /root/portainer.yaml portainer
```

### Reset Senha Portainer
```bash
# Parar Portainer
docker service scale portainer_portainer=0

# Reset
docker pull portainer/helper-reset-password
docker run --rm -v /var/lib/docker/volumes/portainer_data/_data:/data portainer/helper-reset-password

# Subir novamente
docker stack deploy --prune --resolve-image always -c /root/portainer.yaml portainer
```

### Atualizar Traefik
```bash
docker stack deploy --prune --resolve-image always -c /root/traefik.yaml traefik
```

### Ver Logs do Traefik
```bash
docker service logs traefik_traefik --tail 100 -f
```

### Ver Logs do Portainer
```bash
docker service logs portainer_portainer --tail 100 -f
```

### Reiniciar PostgreSQL
```bash
docker service update --force $(docker service ls --filter name='postgres_postgres' -q)
```

### Reiniciar Evolution API
```bash
docker service update --force $(docker service ls --filter name='evolution_evolution_api' -q)
docker service update --force $(docker service ls --filter name='evolution_evolution_redis' -q)
```

### Atualizar Evolution API
```bash
docker stack deploy --prune --resolve-image always -c /root/evolution.yaml evolution
```

### Ver Logs da Evolution API
```bash
docker service logs evolution_evolution_api --tail 100 -f
```

### Ver Logs do Redis (Evolution)
```bash
docker service logs evolution_evolution_redis --tail 50 -f
```

### Criar Banco Adicional no PostgreSQL
```bash
CONTAINER_ID=$(docker ps -q --filter "name=^postgres_postgres")
docker exec "$CONTAINER_ID" psql -U postgres -c "CREATE DATABASE nome_do_banco;"
```

### Listar Bancos no PostgreSQL
```bash
CONTAINER_ID=$(docker ps -q --filter "name=^postgres_postgres")
docker exec "$CONTAINER_ID" psql -U postgres -lqt
```

---

## Deploy de Novos Serviços via Portainer API

Use a API do Portainer para deploy automatizado:

```bash
# 1. Obter token
TOKEN=$(curl -k -s -X POST "https://$VPS_PORTAINER_DOMAIN/api/auth" \
  -H "Content-Type: application/json" \
  -d "{\"username\":\"$VPS_PORTAINER_USER\",\"password\":\"$VPS_PORTAINER_PASS\"}" | jq -r .jwt)

# 2. Listar endpoints (stacks)
curl -k -s "https://$VPS_PORTAINER_DOMAIN/api/endpoints" \
  -H "Authorization: Bearer $TOKEN" | jq '.[].Id'

# 3. Deploy stack via API (exemplo)
curl -k -s -X POST "https://$VPS_PORTAINER_DOMAIN/api/stacks/create/swarm/string?endpointId=1" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"name\": \"meu-servico\",
    \"stackFileContent\": \"$(cat /root/meu-servico.yaml | jq -sR .)\"
  }"
```

---

## Habilitar Dashboard do Traefik (Opcional)

Para acessar o painel do Traefik com autenticação:

```bash
# Gerar credenciais BasicAuth
TRAEFIK_USER="admin"
TRAEFIK_PASS="SuaSenhaForte123"
TRAEFIK_DOMAIN="traefik.seudominio.com"
BASICAUTH=$(htpasswd -nbB "$TRAEFIK_USER" "$TRAEFIK_PASS" | sed 's/\$/\$\$/g')

# Recriar traefik.yaml adicionando '--api.insecure=true' no command
# E estas labels extras no deploy:
#   - "traefik.http.routers.traefik.rule=Host(`$TRAEFIK_DOMAIN`)"
#   - "traefik.http.services.traefik.loadbalancer.server.port=8080"
#   - "traefik.http.routers.traefik.tls.certresolver=letsencryptresolver"
#   - "traefik.http.routers.traefik.service=traefik"
#   - "traefik.http.routers.traefik.entrypoints=websecure"
#   - "traefik.http.routers.traefik.priority=1"
#   - "traefik.http.routers.traefik.middlewares=authtraefik"
#   - "traefik.http.middlewares.authtraefik.basicauth.users=$BASICAUTH"

# Redeploy
docker stack deploy --prune --resolve-image always -c /root/traefik.yaml traefik
```

---

## Checklist Final

Após completar todas as etapas, verifique:

```bash
echo "=== Status dos Serviços ==="
docker service ls

echo ""
echo "=== Rede Overlay ==="
docker network ls | grep overlay

echo ""
echo "=== Volumes ==="
docker volume ls

echo ""
echo "=== Nodes do Swarm ==="
docker node ls

echo ""
echo "=== Portas em uso ==="
ss -tlnp | grep -E ':80|:443|:9000|:8080|:5432|:6379'
```

**Resultado esperado:**
| Serviço | Status | URL |
|---------|--------|-----|
| Traefik | 1/1 | (interno, gerencia SSL) |
| Portainer Agent | 1/1 (global) | (interno) |
| Portainer CE | 1/1 | `https://$VPS_PORTAINER_DOMAIN` |
| PostgreSQL 14 | 1/1 | `postgres:5432` (interno) |
| Evolution API | 1/1 | `https://$VPS_EVOLUTION_DOMAIN` |
| Evolution Redis | 1/1 | `redis:6379` (interno) |
| Rede overlay | ativa | `$VPS_NETWORK` |

---

## Troubleshooting

### Docker Swarm não inicia
```bash
# Verificar IP usado
hostname -I

# Forçar com IP específico
docker swarm init --advertise-addr SEU_IP_AQUI
```

### Certificado SSL não gerado
```bash
# Verificar logs do Traefik
docker service logs traefik_traefik 2>&1 | grep -i "acme\|certificate\|error"

# Verificar se porta 80 está acessível externamente
curl -sI http://SEU_DOMINIO
```

### Portainer API não responde
```bash
# Verificar se o container está rodando
docker service ps portainer_portainer

# Testar conectividade
curl -k -s https://$VPS_PORTAINER_DOMAIN/api/status
```

### Container não conecta à rede
```bash
# Listar redes do container
docker inspect CONTAINER_ID | jq '.[].NetworkSettings.Networks'

# Reconectar
docker network connect $VPS_NETWORK CONTAINER_ID
```

### PostgreSQL não inicia
```bash
# Verificar logs
docker service logs postgres_postgres --tail 50

# Verificar volume
docker volume inspect postgres_data

# Se o volume está corrompido, recriar (PERDA DE DADOS!)
# docker volume rm postgres_data && docker volume create postgres_data
# docker stack deploy --prune --resolve-image always -c /root/postgres.yaml postgres
```

### Evolution API não conecta ao banco
```bash
# Verificar se o banco existe
CONTAINER_ID=$(docker ps -q --filter "name=^postgres_postgres")
docker exec "$CONTAINER_ID" psql -U postgres -lqt | grep evolution
# Se não existe, criar:
docker exec "$CONTAINER_ID" psql -U postgres -c "CREATE DATABASE evolution;"

# Verificar logs da Evolution
docker service logs evolution_evolution_api --tail 50 2>&1 | grep -i "database\|error\|postgres"
```

### Evolution API retorna 401/403
```bash
# Verificar API Key configurada no YAML
grep "AUTHENTICATION_API_KEY" /root/evolution.yaml

# Testar com a API Key correta
curl -sk -H "apikey: SUA_API_KEY" "https://$VPS_EVOLUTION_DOMAIN/instance/fetchInstances"
```

### Redis da Evolution não inicia
```bash
docker service logs evolution_evolution_redis --tail 30
# Verificar volume
docker volume inspect evolution_redis
```

---

## Dicas

1. **Sempre use `docker stack deploy`** em vez de `docker compose up` — Swarm gerencia réplicas, restart e rolling updates
2. **Todo serviço** precisa estar na rede overlay `$VPS_NETWORK` para o Traefik fazer proxy
3. **Labels Traefik** obrigatórias para cada serviço: `traefik.enable=true`, `Host()`, `server.port`, `certresolver`, `entrypoints=websecure`
4. **Nunca exponha portas** diretamente — use Traefik como proxy reverso
5. **Salve os YAMLs** em `/root/` para facilitar redeploys futuros
6. **Portainer API** permite deploy automatizado sem acessar o painel web