README aggiunge sezione 'CI/CD pipeline' che descrive i 5 job e i tag image. DEPLOYMENT espande sez. 1 con dettagli runner Gitea (network gitea_gitea-internal, image runner-images, label ubuntu-latest) e configurazione secret user-level REGISTRY_TOKEN con scope write:package.
9.5 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 5 job:
- lint (
ruff check services/) — gating, 0 violations. - typecheck (
mypy services/common/src/mcp_common) — gating sul modulo comune, warn-only sui servizi. - test (
pytest services/) — gating, 478 test verdi. - validate-config —
docker compose config -qsudocker-compose.ymledocker-compose.prod.yml+caddy validate --config Caddyfile. - build-and-push — solo su push a
main(skip su PR):docker login git.tielogic.xyzcon${{ secrets.REGISTRY_TOKEN }}(PAT scopewrite:package, configurato user-level su Gitea).- Builda e pusha 8 image al registry con tag
:latest+:sha-X:git.tielogic.xyz/adriano/cerbero-mcp/basegit.tielogic.xyz/adriano/cerbero-mcp/gatewaygit.tielogic.xyz/adriano/cerbero-mcp/mcp-{deribit,bybit,hyperliquid,alpaca,macro,sentiment}
- Cache Docker buildx via registry stesso (
buildcache:<name>per ognuna) → run successivi 5-10× più veloci.
Le PR fanno girare solo lint+typecheck+test+validate-config,
niente build/push (gating per merge).
Setup runner Gitea
Il runner act_runner deve girare:
- Sulla stessa Docker network di Gitea (
gitea_gitea-internalo equivalente) altrimentiactions/checkout@v4non risolvegitea:3000per il clone. - Con bind di
/var/run/docker.sockper buildare image dai workflow. - Image dei job mappata a
docker.gitea.com/runner-images:ubuntu-22.04(default Gitea, contiene docker CLI + buildx + git + curl + node). - Label: almeno
ubuntu-latest(usato dal workflow). Altre label (tielogic-ci,ubuntu-22.04, ecc.) opzionali.
Setup secret REGISTRY_TOKEN
Crea un Personal Access Token Gitea con scope write:package (User
Settings → Applications → Generate Token). Aggiungilo come secret a
livello user (User Settings → Secrets → New Secret) con nome
REGISTRY_TOKEN. Tutti i tuoi repo ereditano il secret automaticamente.
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).