CI pipeline (.gitea/workflows/ci.yml): - Job lint (ruff), typecheck (mypy mcp_common gating + servizi warn-only), test (pytest 455). - Job build-and-push solo su main: builda gateway + 6 image MCP via docker/build-push-action@v6, login al registry Gitea con docker/login-action@v3 + secrets.GITEA_TOKEN auto-iniettato. - Cache distribuita type=gha per layer Docker → run successivi 5-10x più veloci. Tag :latest + :sha-XXXXXXX per ogni image. Deploy VPS (docker-compose.prod.yml): - Niente build locale: solo `image:` da git.tielogic.xyz/adriano/ cerbero-mcp/<service>:latest. Variabile IMAGE_TAG per pin a sha specifico. - Servizio Watchtower containerizzato che polla ogni 5min (configurabile via WATCHTOWER_POLL_INTERVAL) e auto-aggiorna i container con label com.centurylinklabs.watchtower.enable=true. Auth registry riusa ~/.docker/config.json bind-mounted readonly. DEPLOYMENT.md: runbook completo per setup VPS, login registry, secrets, .env, smoke test post-deploy, rollback (pin a sha), disable auto-update, nota Traefik upload limit. README aggiornato con link. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.1 KiB
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:
- lint (
ruff check) — gating - typecheck (
mypy mcp_common) — gating su mcp_common, warn-only sui servizi - test (
pytest services/) — gating, 455 test - build-and-push — solo su push a
main:- Logga al registry
git.tielogic.xyzconsecrets.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)
- Logga al registry
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:
echo "$GITEA_PAT" | docker login git.tielogic.xyz -u <gitea-username> --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)
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
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:
# 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
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:
docker pulldella nuova imagedocker stopgraceful del container vecchiodocker rm+ start del nuovo container con stessa config + secret + volumi- 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:
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
# 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
# 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. fail2bansu 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 <service> | 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:
# 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:
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).