chore(V2): rimuovi compose overlay V1 (prod, local, traefik) e DEPLOYMENT.md
Contenuti utili di DEPLOYMENT.md saranno integrati nel nuovo README V2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
-448
@@ -1,448 +0,0 @@
|
||||
# Deployment Cerbero_mcp
|
||||
|
||||
Guida operativa per il deploy della suite MCP su un VPS pubblico.
|
||||
L'architettura è: Gitea ospita codice + container registry; le immagini
|
||||
vengono buildate e pushate dalla **macchina di sviluppo** (laptop) verso
|
||||
il registry; il VPS produzione non builda nulla, fa solo pull dei
|
||||
container già pronti e usa Watchtower per il rollover automatico.
|
||||
|
||||
```
|
||||
┌──────────────────────────┐ ┌─────────────────────────┐ ┌──────────────────────────────────┐
|
||||
│ Laptop dev │ │ Gitea git.tielogic.xyz │ │ VPS produzione │
|
||||
│ │ │ │ │ cerbero-mcp.tielogic.xyz │
|
||||
│ build-push.sh ──push──▶ │───▶│ ┌────────────────────┐ │ │ │
|
||||
│ (8 image) │ │ │ Container registry │ │ │ ┌────────────────────────────┐ │
|
||||
│ git push ─────────────▶ │───▶│ └────────────────────┘ │◀──┼──┤ docker compose │ │
|
||||
│ │ │ ┌────────────────────┐ │ pull │ (docker-compose.prod.yml) │ │
|
||||
│ │ │ │ Cerbero-mcp repo │ │ │ │ gateway, mcp-* │ │
|
||||
│ │ │ └────────────────────┘ │ │ │ watchtower (poll 5min) │ │
|
||||
│ │ │ │ │ └────────────────────────────┘ │
|
||||
└──────────────────────────┘ └─────────────────────────┘ └──────────────────────────────────┘
|
||||
```
|
||||
|
||||
Niente CI/CD su Gitea — qualità e build sono responsabilità del laptop
|
||||
prima del push (lint/test in locale, poi `scripts/build-push.sh`).
|
||||
|
||||
## 1. Build & push image (dal laptop)
|
||||
|
||||
Lo script `scripts/build-push.sh` builda e pusha le 8 image al registry
|
||||
Gitea, replicando il vecchio job CI ma in locale. Pre-requisiti:
|
||||
|
||||
- `docker` + `buildx` sul laptop.
|
||||
- Personal Access Token Gitea con scope `write:package` (User Settings
|
||||
→ Applications → Generate Token).
|
||||
|
||||
```bash
|
||||
export GITEA_PAT='<PAT_write:package>'
|
||||
export GITEA_USER=adriano
|
||||
|
||||
# Tutte le 8 image (base + gateway + 6 mcp-*)
|
||||
./scripts/build-push.sh
|
||||
|
||||
# Solo specifiche (es. dopo modifica a un singolo servizio)
|
||||
./scripts/build-push.sh base mcp-bybit
|
||||
```
|
||||
|
||||
Lo script:
|
||||
- Fa `docker login git.tielogic.xyz`.
|
||||
- Builda con `docker buildx build --push` (cache buildx locale del
|
||||
laptop, niente cache registry: build successivi rapidi senza pesare
|
||||
sul registry).
|
||||
- Tagga `:latest` + `:sha-<short_HEAD>`.
|
||||
- Per le mcp-* passa `BASE_IMAGE`/`BASE_TAG` come build-arg in modo da
|
||||
ereditare dall'image `base` appena pushata.
|
||||
|
||||
Ordine consigliato: builda `base` prima delle `mcp-*` (lo script lo fa
|
||||
di default se chiamato senza argomenti).
|
||||
|
||||
## 1b. Quality gate locale (consigliato prima del push)
|
||||
|
||||
Prima di `build-push.sh` esegui in locale i check che prima girava il CI:
|
||||
|
||||
```bash
|
||||
uv run ruff check services/
|
||||
uv run mypy services/common/src/mcp_common
|
||||
uv run pytest services/ --tb=short
|
||||
docker compose -f docker-compose.prod.yml config -q
|
||||
```
|
||||
|
||||
Tutti devono essere verdi prima di pushare image al registry.
|
||||
|
||||
## 2a. Topologia: standalone vs behind-Traefik
|
||||
|
||||
Cerbero_mcp supporta due topologie di deploy:
|
||||
|
||||
### Standalone (Caddy gestisce TLS direttamente)
|
||||
|
||||
```
|
||||
Internet ──[443]──► Caddy gateway ──► mcp-* services
|
||||
(ACME Let's Encrypt)
|
||||
```
|
||||
|
||||
Setto: `docker-compose.prod.yml` da solo. Caddy bind sulle porte
|
||||
80/443 host, fa cert auto via ACME. Adatto a un VPS dedicato senza
|
||||
altri servizi sulle 80/443.
|
||||
|
||||
### Behind-Traefik (Traefik termina TLS)
|
||||
|
||||
```
|
||||
Internet ──[443]──► Traefik ──[traefik network]──► Caddy gateway ──► mcp-* services
|
||||
(TLS+ACME) (rate-limit, IP allowlist)
|
||||
```
|
||||
|
||||
Setto: `docker-compose.prod.yml` + `docker-compose.traefik.yml` overlay.
|
||||
Caddy non bind su host, ascolta plain HTTP `:80` interno alla
|
||||
`traefik` network. Traefik fa routing per `Host(cerbero-mcp.tielogic.xyz)`,
|
||||
TLS, ACME. Adatto a VPS condiviso con altri servizi (Gitea, ecc.).
|
||||
|
||||
## 2. Deploy automatizzato (script no-clone)
|
||||
|
||||
Il modo più rapido è `scripts/deploy-noclone.sh`, idempotente. Sul VPS
|
||||
**non** viene clonato il repo: lo script scarica via raw HTTP solo i
|
||||
file strettamente necessari al runtime (compose, Caddyfile, public
|
||||
assets). Esegui sul VPS:
|
||||
|
||||
```bash
|
||||
# Prerequisiti
|
||||
export GITEA_PAT="<PAT con scope read:package>"
|
||||
export GITEA_USER=adriano
|
||||
|
||||
# Crea la dir di deploy e mettici i secrets via scp dal posto sicuro
|
||||
sudo mkdir -p /docker/cerbero_mcp/secrets
|
||||
sudo chown -R "$USER" /docker/cerbero_mcp
|
||||
# scp deribit.json bybit.json hyperliquid.json alpaca.json \
|
||||
# macro.json sentiment.json core.token observer.token \
|
||||
# vps:/docker/cerbero_mcp/secrets/
|
||||
|
||||
# Behind Traefik (opzionale, solo se VPS condiviso con Gitea o altri)
|
||||
# export BEHIND_TRAEFIK=true
|
||||
# export TRAEFIK_NETWORK=gitea_traefik-public
|
||||
|
||||
curl -sL -o /tmp/deploy-noclone.sh \
|
||||
https://git.tielogic.xyz/Adriano/Cerbero-mcp/raw/branch/main/scripts/deploy-noclone.sh
|
||||
chmod +x /tmp/deploy-noclone.sh
|
||||
/tmp/deploy-noclone.sh
|
||||
```
|
||||
|
||||
Lo script esegue: docker login registry → scarica `docker-compose.prod.yml`,
|
||||
`docker-compose.traefik.yml`, `gateway/Caddyfile`, `gateway/public/*` in
|
||||
`/docker/cerbero_mcp/` → chmod 600 sui secrets → genera `.env` iniziale
|
||||
(testnet) → crea `/var/log/cerbero-mcp` con permessi `1000:1000` → pull
|
||||
image dal registry → `docker compose up -d` → smoke test pubblico.
|
||||
|
||||
Per aggiornare in seguito: ri-esegui lo stesso script (preserva `.env`
|
||||
e secrets, ricarica config dal branch `main` aggiornato).
|
||||
|
||||
**Override paths**: `DEPLOY_DIR` (default `/docker/cerbero_mcp`),
|
||||
`SECRETS_SRC` (default `$DEPLOY_DIR/secrets`), `AUDIT_LOG_DIR` (default
|
||||
`/var/log/cerbero-mcp`).
|
||||
|
||||
**Override compose locale (`docker-compose.local.yml`)**: lo script
|
||||
include automaticamente come ultimo `-f` un eventuale
|
||||
`$DEPLOY_DIR/docker-compose.local.yml`. Utile per fix specifici della
|
||||
macchina (es. forzare `DOCKER_API_VERSION` su watchtower se il daemon
|
||||
del VPS è più vecchio dell'API attesa). File gitignored per design —
|
||||
non viene scaricato dal repo, lo crei a mano sul VPS. Esempio:
|
||||
|
||||
```yaml
|
||||
# /docker/cerbero_mcp/docker-compose.local.yml
|
||||
services:
|
||||
watchtower:
|
||||
environment:
|
||||
DOCKER_API_VERSION: "1.44"
|
||||
```
|
||||
|
||||
### Modalità behind-Traefik
|
||||
|
||||
Se sul VPS gira già un Traefik (es. lo stesso VPS di Gitea), prima di
|
||||
lanciare lo script aggiungi al tuo `.env`:
|
||||
|
||||
```bash
|
||||
BEHIND_TRAEFIK=true
|
||||
TRAEFIK_NETWORK=gitea_traefik-public # nome network esterna di Traefik
|
||||
TRAEFIK_CERTRESOLVER=letsencrypt # nome resolver in Traefik
|
||||
TRAEFIK_ENTRYPOINT=websecure # entrypoint HTTPS Traefik
|
||||
|
||||
# Porte gateway non più necessarie (Traefik bind 80/443):
|
||||
# GATEWAY_HTTP_PORT, GATEWAY_HTTPS_PORT non vengono usate.
|
||||
```
|
||||
|
||||
Lo script rileva `BEHIND_TRAEFIK=true` e usa
|
||||
`docker compose -f docker-compose.prod.yml -f docker-compose.traefik.yml`.
|
||||
Il gateway Caddy NON bind su 80/443 host; viene esposto via Traefik con
|
||||
labels per `Host(cerbero-mcp.tielogic.xyz)`.
|
||||
|
||||
Verifica della network Traefik:
|
||||
|
||||
```bash
|
||||
docker network ls | grep -i traefik
|
||||
# Tipicamente vedrai: gitea_traefik-public, traefik_default, ecc.
|
||||
# Usa il nome ESATTO come TRAEFIK_NETWORK in .env.
|
||||
```
|
||||
|
||||
## 3. Safety: switch testnet → mainnet
|
||||
|
||||
`mcp_common.environment.consistency_check` (richiamato dal boot
|
||||
`run_exchange_main`) PREVIENE switch accidentali:
|
||||
|
||||
- Se l'ambiente risolto è **mainnet** ma il secret JSON corrispondente
|
||||
non contiene `"environment": "mainnet"` esplicito → boot abort con
|
||||
`EnvironmentMismatchError`.
|
||||
- Se il secret dichiara un environment diverso da quello risolto (es.
|
||||
`creds["environment"]="mainnet"` ma env var setta testnet) → boot abort.
|
||||
|
||||
**Per passare a mainnet su un exchange specifico** (es. bybit):
|
||||
|
||||
1. Edita `secrets/bybit.json`: aggiungi `"environment": "mainnet"`.
|
||||
2. Modifica `.env`: `BYBIT_TESTNET=false`.
|
||||
3. `docker compose -f docker-compose.prod.yml --env-file .env restart mcp-bybit`.
|
||||
|
||||
Senza il flag esplicito nel secret, il container mcp-bybit fallirà al
|
||||
boot e Watchtower NON aggiornerà su versioni con cred mainnet rotti.
|
||||
|
||||
Override `STRICT_MAINNET=false` in `.env` permette mainnet senza la
|
||||
conferma esplicita (downgrade safety, sconsigliato in produzione).
|
||||
|
||||
## 4. Audit log persistente
|
||||
|
||||
Tutti i write endpoint (`place_order`, `place_combo_order`, `cancel_*`,
|
||||
`set_*`, `close_*`, `transfer_*`, `amend_*`, `switch_*`) emettono un
|
||||
record JSON strutturato sul logger `mcp.audit`.
|
||||
|
||||
**Sink**:
|
||||
- stdout/stderr container (sempre, visibile via `docker logs`).
|
||||
- File JSONL persistente su volume host:
|
||||
`${AUDIT_LOG_DIR:-/var/log/cerbero-mcp}/<service>.audit.jsonl`.
|
||||
Rotation a mezzanotte UTC con retention `AUDIT_LOG_BACKUP_DAYS`
|
||||
(default 30 giorni).
|
||||
|
||||
**Esempio record**:
|
||||
|
||||
```json
|
||||
{
|
||||
"audit_event": "write_op",
|
||||
"action": "place_order",
|
||||
"exchange": "bybit",
|
||||
"principal": "core",
|
||||
"target": "BTCUSDT",
|
||||
"payload": {"side": "Buy", "qty": 0.01, "price": 60000, "leverage": 3},
|
||||
"result": {"order_id": "abc123", "status": "submitted"}
|
||||
}
|
||||
```
|
||||
|
||||
**Query operative**:
|
||||
|
||||
```bash
|
||||
# Tutto l'audit log oggi
|
||||
tail -f /var/log/cerbero-mcp/*.audit.jsonl
|
||||
|
||||
# Solo place_order su bybit
|
||||
jq -c 'select(.action=="place_order" and .exchange=="bybit")' \
|
||||
/var/log/cerbero-mcp/bybit.audit.jsonl
|
||||
|
||||
# Errori
|
||||
jq -c 'select(.error)' /var/log/cerbero-mcp/*.audit.jsonl
|
||||
|
||||
# Operazioni di un principal
|
||||
jq -c 'select(.principal=="core")' /var/log/cerbero-mcp/*.audit.jsonl
|
||||
```
|
||||
|
||||
I secret (api_key, password) sono filtrati automaticamente da
|
||||
`SecretsFilter` prima di arrivare al sink.
|
||||
|
||||
## 5. Setup iniziale del VPS (manuale, alternativa allo script)
|
||||
|
||||
**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 <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) Crea dir di deploy e scarica i file di config
|
||||
|
||||
Sul VPS NON serve clonare il repo. Bastano i file di compose, il
|
||||
`Caddyfile` e i public assets del gateway:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /docker/cerbero_mcp/{secrets,gateway/public}
|
||||
sudo chown -R "$USER" /docker/cerbero_mcp
|
||||
cd /docker/cerbero_mcp
|
||||
|
||||
BASE=https://git.tielogic.xyz/Adriano/Cerbero-mcp/raw/branch/main
|
||||
curl -fsSL -o docker-compose.prod.yml $BASE/docker-compose.prod.yml
|
||||
curl -fsSL -o docker-compose.traefik.yml $BASE/docker-compose.traefik.yml
|
||||
curl -fsSL -o gateway/Caddyfile $BASE/gateway/Caddyfile
|
||||
curl -fsSL -o gateway/public/index.html $BASE/gateway/public/index.html
|
||||
curl -fsSL -o gateway/public/status.js $BASE/gateway/public/status.js
|
||||
curl -fsSL -o gateway/public/style.css $BASE/gateway/public/style.css
|
||||
```
|
||||
|
||||
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 `/docker/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`.
|
||||
|
||||
## 6. 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.
|
||||
|
||||
## 7. 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.
|
||||
|
||||
## 8. 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
|
||||
```
|
||||
|
||||
## 9. 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 <service> | grep audit_event` — per
|
||||
produzione meglio redirezionare a syslog o a un servizio dedicato.
|
||||
|
||||
## 10. 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.
|
||||
|
||||
## 11. Aggiornamento del compose stesso (file YAML)
|
||||
|
||||
Watchtower aggiorna le **image**, non `docker-compose.prod.yml` né
|
||||
`Caddyfile`. Se cambi struttura (nuovi servizi, nuove env var, modifiche
|
||||
al gateway), ri-esegui sul VPS lo script no-clone, che ri-scarica i file
|
||||
di config dal branch `main` di Gitea e applica:
|
||||
|
||||
```bash
|
||||
/tmp/deploy-noclone.sh
|
||||
```
|
||||
|
||||
Lo script è idempotente: preserva `.env` e `secrets/`, aggiorna solo i
|
||||
file di config + fa `pull` + `up -d`.
|
||||
@@ -1,205 +0,0 @@
|
||||
# docker-compose.prod.yml — deploy su VPS produzione.
|
||||
#
|
||||
# Differenze vs docker-compose.yml (dev):
|
||||
# - Niente `build:`, solo `image:` dal registry Gitea.
|
||||
# - Tag `latest` (Watchtower polla per nuove versioni).
|
||||
# - Aggiunge servizio `watchtower` che auto-aggiorna i container etichettati
|
||||
# `com.centurylinklabs.watchtower.enable=true` quando il tag latest cambia.
|
||||
# - Auth registry: `docker login git.tielogic.xyz` una sola volta sull'host
|
||||
# (Watchtower legge ~/.docker/config.json bind-mounted in /config.json).
|
||||
#
|
||||
# Uso sul VPS:
|
||||
# docker login git.tielogic.xyz
|
||||
# docker compose -f docker-compose.prod.yml --env-file .env up -d
|
||||
#
|
||||
# Override variabili in `.env` accanto al compose:
|
||||
# ACME_EMAIL=adrianodalpastro@tielogic.com
|
||||
# WRITE_ALLOWLIST="127.0.0.1/32 ::1/128 172.16.0.0/12"
|
||||
# GATEWAY_HTTP_PORT=80
|
||||
# GATEWAY_HTTPS_PORT=443
|
||||
# IMAGE_TAG=latest # o sha-XXXXXXX per pin specifico
|
||||
|
||||
networks:
|
||||
internal:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
caddy-data:
|
||||
caddy-config:
|
||||
|
||||
secrets:
|
||||
deribit_credentials:
|
||||
file: ./secrets/deribit.json
|
||||
hyperliquid_wallet:
|
||||
file: ./secrets/hyperliquid.json
|
||||
bybit_credentials:
|
||||
file: ./secrets/bybit.json
|
||||
alpaca_credentials:
|
||||
file: ./secrets/alpaca.json
|
||||
macro_credentials:
|
||||
file: ./secrets/macro.json
|
||||
sentiment_credentials:
|
||||
file: ./secrets/sentiment.json
|
||||
core_token:
|
||||
file: ./secrets/core.token
|
||||
observer_token:
|
||||
file: ./secrets/observer.token
|
||||
|
||||
x-common-security: &common-security
|
||||
cap_drop: [ALL]
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
restart: unless-stopped
|
||||
networks: [internal]
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
volumes:
|
||||
- ${AUDIT_LOG_DIR:-/var/log/cerbero-mcp}:/var/log/cerbero-mcp:rw
|
||||
|
||||
x-image-prefix: &image_prefix git.tielogic.xyz/adriano/cerbero-mcp
|
||||
|
||||
services:
|
||||
gateway:
|
||||
image: ${IMAGE_PREFIX:-git.tielogic.xyz/adriano/cerbero-mcp}/gateway:${IMAGE_TAG:-latest}
|
||||
restart: unless-stopped
|
||||
networks: [internal]
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
ports:
|
||||
- "${GATEWAY_HTTP_PORT:-80}:80"
|
||||
- "${GATEWAY_HTTPS_PORT:-443}:443"
|
||||
environment:
|
||||
ACME_EMAIL: ${ACME_EMAIL:-adrianodalpastro@tielogic.com}
|
||||
WRITE_ALLOWLIST: ${WRITE_ALLOWLIST:-127.0.0.1/32 ::1/128 172.16.0.0/12}
|
||||
volumes:
|
||||
- ./gateway/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./gateway/public:/srv:ro
|
||||
- caddy-data:/data
|
||||
- caddy-config:/config
|
||||
depends_on:
|
||||
mcp-deribit: { condition: service_healthy }
|
||||
mcp-hyperliquid: { condition: service_healthy }
|
||||
mcp-bybit: { condition: service_healthy }
|
||||
mcp-alpaca: { condition: service_healthy }
|
||||
mcp-macro: { condition: service_healthy }
|
||||
mcp-sentiment: { condition: service_healthy }
|
||||
healthcheck:
|
||||
test: ["CMD", "wget", "-q", "--spider", "http://localhost/"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
|
||||
mcp-deribit:
|
||||
image: ${IMAGE_PREFIX:-git.tielogic.xyz/adriano/cerbero-mcp}/mcp-deribit:${IMAGE_TAG:-latest}
|
||||
<<: *common-security
|
||||
user: "1000:1000"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,size=64M,mode=1777
|
||||
secrets: [deribit_credentials, core_token, observer_token]
|
||||
environment:
|
||||
CREDENTIALS_FILE: /run/secrets/deribit_credentials
|
||||
CORE_TOKEN_FILE: /run/secrets/core_token
|
||||
OBSERVER_TOKEN_FILE: /run/secrets/observer_token
|
||||
DERIBIT_TESTNET: "${DERIBIT_TESTNET:-true}"
|
||||
ROOT_PATH: /mcp-deribit
|
||||
AUDIT_LOG_FILE: /var/log/cerbero-mcp/deribit.audit.jsonl
|
||||
|
||||
mcp-hyperliquid:
|
||||
image: ${IMAGE_PREFIX:-git.tielogic.xyz/adriano/cerbero-mcp}/mcp-hyperliquid:${IMAGE_TAG:-latest}
|
||||
<<: *common-security
|
||||
user: "1000:1000"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,size=64M,mode=1777
|
||||
secrets: [hyperliquid_wallet, core_token, observer_token]
|
||||
environment:
|
||||
HYPERLIQUID_WALLET_FILE: /run/secrets/hyperliquid_wallet
|
||||
CORE_TOKEN_FILE: /run/secrets/core_token
|
||||
OBSERVER_TOKEN_FILE: /run/secrets/observer_token
|
||||
HYPERLIQUID_TESTNET: "${HYPERLIQUID_TESTNET:-true}"
|
||||
ROOT_PATH: /mcp-hyperliquid
|
||||
AUDIT_LOG_FILE: /var/log/cerbero-mcp/hyperliquid.audit.jsonl
|
||||
|
||||
mcp-bybit:
|
||||
image: ${IMAGE_PREFIX:-git.tielogic.xyz/adriano/cerbero-mcp}/mcp-bybit:${IMAGE_TAG:-latest}
|
||||
<<: *common-security
|
||||
user: "1000:1000"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,size=64M,mode=1777
|
||||
secrets: [bybit_credentials, core_token, observer_token]
|
||||
environment:
|
||||
BYBIT_CREDENTIALS_FILE: /run/secrets/bybit_credentials
|
||||
CORE_TOKEN_FILE: /run/secrets/core_token
|
||||
OBSERVER_TOKEN_FILE: /run/secrets/observer_token
|
||||
BYBIT_TESTNET: "${BYBIT_TESTNET:-true}"
|
||||
ROOT_PATH: /mcp-bybit
|
||||
AUDIT_LOG_FILE: /var/log/cerbero-mcp/bybit.audit.jsonl
|
||||
PORT: "9019"
|
||||
|
||||
mcp-alpaca:
|
||||
image: ${IMAGE_PREFIX:-git.tielogic.xyz/adriano/cerbero-mcp}/mcp-alpaca:${IMAGE_TAG:-latest}
|
||||
<<: *common-security
|
||||
user: "1000:1000"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,size=64M,mode=1777
|
||||
secrets: [alpaca_credentials, core_token, observer_token]
|
||||
environment:
|
||||
ALPACA_CREDENTIALS_FILE: /run/secrets/alpaca_credentials
|
||||
CORE_TOKEN_FILE: /run/secrets/core_token
|
||||
OBSERVER_TOKEN_FILE: /run/secrets/observer_token
|
||||
ALPACA_PAPER: "${ALPACA_PAPER:-true}"
|
||||
ROOT_PATH: /mcp-alpaca
|
||||
AUDIT_LOG_FILE: /var/log/cerbero-mcp/alpaca.audit.jsonl
|
||||
PORT: "9020"
|
||||
|
||||
mcp-macro:
|
||||
image: ${IMAGE_PREFIX:-git.tielogic.xyz/adriano/cerbero-mcp}/mcp-macro:${IMAGE_TAG:-latest}
|
||||
<<: *common-security
|
||||
user: "1000:1000"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,size=64M,mode=1777
|
||||
secrets: [macro_credentials, core_token, observer_token]
|
||||
environment:
|
||||
MACRO_CREDENTIALS_FILE: /run/secrets/macro_credentials
|
||||
CORE_TOKEN_FILE: /run/secrets/core_token
|
||||
OBSERVER_TOKEN_FILE: /run/secrets/observer_token
|
||||
ROOT_PATH: /mcp-macro
|
||||
|
||||
mcp-sentiment:
|
||||
image: ${IMAGE_PREFIX:-git.tielogic.xyz/adriano/cerbero-mcp}/mcp-sentiment:${IMAGE_TAG:-latest}
|
||||
<<: *common-security
|
||||
user: "1000:1000"
|
||||
read_only: true
|
||||
tmpfs:
|
||||
- /tmp:rw,size=64M,mode=1777
|
||||
secrets: [sentiment_credentials, core_token, observer_token]
|
||||
environment:
|
||||
SENTIMENT_CREDENTIALS_FILE: /run/secrets/sentiment_credentials
|
||||
CORE_TOKEN_FILE: /run/secrets/core_token
|
||||
OBSERVER_TOKEN_FILE: /run/secrets/observer_token
|
||||
ROOT_PATH: /mcp-sentiment
|
||||
|
||||
# ========================================================
|
||||
# WATCHTOWER — auto-update container con label enable=true
|
||||
# ========================================================
|
||||
watchtower:
|
||||
image: containrrr/watchtower:latest
|
||||
restart: unless-stopped
|
||||
networks: [internal]
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ${HOME}/.docker/config.json:/config.json:ro
|
||||
environment:
|
||||
WATCHTOWER_LABEL_ENABLE: "true"
|
||||
WATCHTOWER_CLEANUP: "true"
|
||||
WATCHTOWER_POLL_INTERVAL: "${WATCHTOWER_POLL_INTERVAL:-300}"
|
||||
WATCHTOWER_INCLUDE_RESTARTING: "false"
|
||||
WATCHTOWER_NOTIFICATIONS_LEVEL: info
|
||||
WATCHTOWER_LOG_LEVEL: info
|
||||
command: --interval ${WATCHTOWER_POLL_INTERVAL:-300}
|
||||
@@ -1,60 +0,0 @@
|
||||
# docker-compose.traefik.yml — overlay per integrare Cerbero_mcp con un
|
||||
# Traefik già esistente sull'host (es. lo stesso VPS che ospita Gitea).
|
||||
#
|
||||
# USO:
|
||||
# docker compose -f docker-compose.prod.yml -f docker-compose.traefik.yml \
|
||||
# --env-file .env up -d
|
||||
#
|
||||
# Differenze vs docker-compose.prod.yml standalone:
|
||||
# - Gateway Caddy NON ha ports binding host (Traefik è il punto di ingresso
|
||||
# pubblico su 80/443).
|
||||
# - Gateway è connesso anche alla network esterna `traefik` (override env
|
||||
# TRAEFIK_NETWORK se diversa, es. `gitea_traefik-public`).
|
||||
# - Caddy NON fa auto-TLS — Traefik termina TLS e fa ACME Let's Encrypt.
|
||||
# Caddy ascolta in chiaro su :80 dentro Docker network.
|
||||
# - Trusted proxies: Caddy rispetta X-Forwarded-For ricevuto da Traefik
|
||||
# per il match `remote_ip` (rate limit + WRITE_ALLOWLIST).
|
||||
# - Labels Traefik su gateway: routing Host(`cerbero-mcp.tielogic.xyz`) +
|
||||
# TLS automatic.
|
||||
#
|
||||
# Variabili .env aggiuntive richieste:
|
||||
# TRAEFIK_NETWORK=gitea_traefik-public # nome network di Traefik
|
||||
# TRAEFIK_CERTRESOLVER=letsencrypt # nome resolver in tua config Traefik
|
||||
# TRAEFIK_ENTRYPOINT=websecure # entrypoint HTTPS Traefik
|
||||
|
||||
networks:
|
||||
traefik:
|
||||
external: true
|
||||
name: ${TRAEFIK_NETWORK:-gitea_traefik-public}
|
||||
|
||||
services:
|
||||
gateway:
|
||||
# Override: niente port binding host, traffica solo via Traefik
|
||||
ports: !reset []
|
||||
networks:
|
||||
- internal
|
||||
- traefik
|
||||
environment:
|
||||
ACME_EMAIL: ${ACME_EMAIL:-adrianodalpastro@tielogic.com}
|
||||
WRITE_ALLOWLIST: ${WRITE_ALLOWLIST:-127.0.0.1/32 ::1/128 172.16.0.0/12}
|
||||
# Mode behind-proxy: Caddy ascolta plain HTTP su :80, no auto_https
|
||||
LISTEN: ":80"
|
||||
AUTO_HTTPS: "off"
|
||||
# Traefik è il proxy che inoltra; trusta range privati + opz. CIDR Traefik
|
||||
TRUSTED_PROXIES: ${TRUSTED_PROXIES:-private_ranges}
|
||||
labels:
|
||||
com.centurylinklabs.watchtower.enable: "true"
|
||||
traefik.enable: "true"
|
||||
traefik.docker.network: ${TRAEFIK_NETWORK:-gitea_traefik-public}
|
||||
traefik.http.routers.cerbero-mcp.rule: "Host(`cerbero-mcp.tielogic.xyz`)"
|
||||
traefik.http.routers.cerbero-mcp.entrypoints: ${TRAEFIK_ENTRYPOINT:-websecure}
|
||||
traefik.http.routers.cerbero-mcp.tls: "true"
|
||||
traefik.http.routers.cerbero-mcp.tls.certresolver: ${TRAEFIK_CERTRESOLVER:-letsencrypt}
|
||||
traefik.http.services.cerbero-mcp.loadbalancer.server.port: "80"
|
||||
# Security headers a livello Traefik (ridondante con Caddy ma utile se
|
||||
# in futuro Caddy viene rimosso). Commenta se non vuoi duplicazione.
|
||||
traefik.http.routers.cerbero-mcp.middlewares: cerbero-mcp-secheaders@docker
|
||||
traefik.http.middlewares.cerbero-mcp-secheaders.headers.stsSeconds: "31536000"
|
||||
traefik.http.middlewares.cerbero-mcp-secheaders.headers.stsIncludeSubdomains: "true"
|
||||
traefik.http.middlewares.cerbero-mcp-secheaders.headers.contentTypeNosniff: "true"
|
||||
traefik.http.middlewares.cerbero-mcp-secheaders.headers.referrerPolicy: "no-referrer"
|
||||
Reference in New Issue
Block a user