# Deployment Cerbero_mcp Guida operativa per il deploy della suite MCP su un VPS pubblico. L'architettura è: Gitea ospita codice + container registry; il VPS produzione non builda nulla, ma fa pull dei container già pronti dalla registry e usa Watchtower per il rollover automatico delle versioni. ``` ┌─────────────────────────┐ ┌──────────────────────────────────┐ │ Gitea git.tielogic.xyz │ │ VPS produzione │ │ │ │ cerbero-mcp.tielogic.xyz │ │ ┌──────────────────┐ │ push │ │ │ │ Cerbero-mcp repo │───┼─CI/CD──▶│ ┌────────────────────────────┐ │ │ └──────────────────┘ │ image │ │ docker compose │ │ │ ┌──────────────────┐ │ │ │ (docker-compose.prod.yml) │ │ │ │ Container reg. │◀──┼─ pull ──┤ │ gateway, mcp-* │ │ │ └──────────────────┘ │ │ │ watchtower (poll 5min) │ │ │ ┌──────────────────┐ │ │ └────────────────────────────┘ │ │ │ Actions runner │ │ │ │ │ └──────────────────┘ │ │ │ └─────────────────────────┘ └──────────────────────────────────┘ ``` ## 1. Pipeline CI/CD (Gitea Actions) `.gitea/workflows/ci.yml` ad ogni push su `main` esegue, in sequenza: 1. **lint** (`ruff check`) — gating 2. **typecheck** (`mypy mcp_common`) — gating su mcp_common, warn-only sui servizi 3. **test** (`pytest services/`) — gating, 455 test 4. **build-and-push** — solo su push a `main`: - Logga al registry `git.tielogic.xyz` con `secrets.GITEA_TOKEN` - Builda `docker/base.Dockerfile` (cache) - Builda e pusha `gateway` + 6 servizi MCP con tag: - `:latest` (mobile, Watchtower polla questo) - `:sha-XXXXXXX` (immutabile, per rollback puntuali) Le PR fanno girare solo lint+typecheck+test, niente build/push. ## 2. Setup iniziale del VPS **Pre-requisiti**: Docker Engine ≥ 24, `docker compose` plugin, accesso SSH sudo, dominio DNS A record `cerbero-mcp.tielogic.xyz` → IP del VPS, porte 80 e 443 aperte sul firewall (per ACME challenge + traffico HTTPS). ### a) Login al registry Gitea Crea un Personal Access Token su Gitea (`Settings → Applications → Generate new token`) con scope `read:package`. Quindi sul VPS: ```bash echo "$GITEA_PAT" | docker login git.tielogic.xyz -u --password-stdin ``` Le credenziali vengono salvate in `~/.docker/config.json`. Watchtower lo bind-monta in sola lettura per fare i pull autenticati. ### b) Clona repository (solo per i file di compose, secret e Caddyfile) ```bash sudo mkdir -p /opt/cerbero-mcp && sudo chown $USER /opt/cerbero-mcp cd /opt/cerbero-mcp git clone ssh://git@git.tielogic.xyz:222/Adriano/Cerbero-mcp.git . ``` Il VPS NON ha bisogno di buildare; usa `docker-compose.prod.yml` che fa solo pull dal registry. ### c) Prepara secrets ```bash mkdir -p secrets # Copia (via scp) i file JSON con cred reali: # secrets/deribit.json, bybit.json, alpaca.json, hyperliquid.json, # secrets/macro.json, sentiment.json # secrets/core.token, observer.token chmod 600 secrets/* ``` ### d) `.env` con configurazione runtime Crea `/opt/cerbero-mcp/.env`: ```bash # Gateway ACME_EMAIL=adrianodalpastro@tielogic.com GATEWAY_HTTP_PORT=80 GATEWAY_HTTPS_PORT=443 WRITE_ALLOWLIST="127.0.0.1/32 ::1/128 172.16.0.0/12" # Image tag — `latest` per auto-update Watchtower, oppure pin a sha-XXXXXXX IMAGE_TAG=latest IMAGE_PREFIX=git.tielogic.xyz/adriano/cerbero-mcp # Environment exchange (true=testnet, false=mainnet) DERIBIT_TESTNET=true BYBIT_TESTNET=true HYPERLIQUID_TESTNET=true ALPACA_PAPER=true # Watchtower polling interval (sec). 300=5min default. WATCHTOWER_POLL_INTERVAL=300 ``` ### e) Avvio ```bash docker compose -f docker-compose.prod.yml --env-file .env pull docker compose -f docker-compose.prod.yml --env-file .env up -d docker compose -f docker-compose.prod.yml logs -f gateway ``` Caddy chiede automaticamente il certificato Let's Encrypt al primo contatto su `https://cerbero-mcp.tielogic.xyz`. ## 3. Auto-update via Watchtower Watchtower (servizio `watchtower` nel compose) polla il registry ogni `WATCHTOWER_POLL_INTERVAL` secondi. Se trova un nuovo digest dietro al tag `:latest` di un container etichettato `com.centurylinklabs.watchtower.enable=true`, fa: 1. `docker pull` della nuova image 2. `docker stop` graceful del container vecchio 3. `docker rm` + start del nuovo container con stessa config + secret + volumi 4. Cleanup image vecchia (`WATCHTOWER_CLEANUP=true`) I container con label sono: `gateway`, `mcp-deribit`, `mcp-bybit`, `mcp-hyperliquid`, `mcp-alpaca`, `mcp-macro`, `mcp-sentiment`. Il container `watchtower` stesso non si auto-aggiorna (per evitare loop). ### Disabilitare auto-update temporaneamente Pin a uno SHA specifico nel `.env`: ```bash IMAGE_TAG=sha-6b7b3f7 docker compose -f docker-compose.prod.yml --env-file .env up -d ``` In questo modo `:latest` non viene più seguito; per riattivare il rollover automatico ripristina `IMAGE_TAG=latest`. ### Disabilitare auto-update per un singolo servizio Rimuovi la label `com.centurylinklabs.watchtower.enable=true` per quel servizio nel compose (oppure imposta `=false`). Watchtower lo ignora ma continua a tenere aggiornati gli altri. ## 4. Rollback ```bash # Trova lo SHA della versione precedente docker images "git.tielogic.xyz/adriano/cerbero-mcp/*" --format "{{.Tag}}" # Pin nel .env IMAGE_TAG=sha-XXXXXXX docker compose -f docker-compose.prod.yml --env-file .env up -d ``` Watchtower NON downgraderà perché il digest del tag pin corrisponde a quello locale. ## 5. Smoke test post-deploy ```bash # Da fuori VPS (laptop) curl -s https://cerbero-mcp.tielogic.xyz/mcp-macro/health # {"status":"ok",...} # Test write endpoint allowlist (deve rispondere 403 da IP esterno): curl -X POST https://cerbero-mcp.tielogic.xyz/mcp-deribit/tools/place_order \ -H "Authorization: Bearer $(cat secrets/core.token)" \ -d '{"instrument_name":"BTC-PERPETUAL","side":"buy","amount":1}' # 403 forbidden: source ip not in allowlist ← OK # Sul VPS: GATEWAY=http://localhost bash tests/smoke/run.sh ``` ## 6. Sicurezza VPS - Firewall `ufw`: `allow 22, 80, 443`. Tutto il resto deny in. - `fail2ban` su SSH e (opz) sul log Caddy 401. - Secret rotation manuale: aggiorna i file `secrets/*.token` → `docker compose restart` (i token vengono ricaricati al boot di ogni servizio MCP). - Audit log in `docker compose logs | grep audit_event` — per produzione meglio redirezionare a syslog o a un servizio dedicato. ## 7. Note Traefik / reverse proxy davanti a Gitea Gitea è esposto via Traefik (ROOT_URL `https://git.tielogic.xyz`). Per il push di image Docker il reverse proxy deve consentire upload di body grossi (un singolo layer può superare i 100MB). Traefik default va bene, ma se vedi `413 Request Entity Too Large` durante `docker push` aumenta il limite nel middleware: ```yaml # traefik dynamic config http: middlewares: gitea-upload: buffering: maxRequestBodyBytes: 524288000 # 500MB ``` Applicalo come middleware al router Gitea. ## 8. Aggiornamento del compose stesso (file YAML) Watchtower aggiorna le **image**, non il `docker-compose.prod.yml`. Se cambi struttura (nuovi servizi, nuove env var) devi: ```bash cd /opt/cerbero-mcp git pull docker compose -f docker-compose.prod.yml --env-file .env up -d ``` Per automatizzare anche questo serve un cron job o uno step CD push-based (vedi backlog).