From 4f3e95980517692e6064dc9eb81efbf32a9dc981 Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Wed, 29 Apr 2026 09:56:07 +0200 Subject: [PATCH] feat(deploy): docker-compose.traefik.yml overlay per behind-Traefik MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Per VPS condiviso (es. con Gitea) dove Traefik gestisce già 80/443. - gateway/Caddyfile: env-aware listen + auto_https + trusted_proxies (defaults invariati per modalità standalone). - docker-compose.traefik.yml: overlay che rimuove ports binding host, attacca gateway alla network esterna di Traefik, set labels per routing Host(cerbero-mcp.tielogic.xyz) + TLS via certresolver Traefik. Caddy ascolta plain HTTP :80 interno. - scripts/deploy.sh: rileva BEHIND_TRAEFIK=true → aggiunge -f docker-compose.traefik.yml a tutti i docker compose call. - DEPLOYMENT.md: nuova sezione 2a (topologia standalone vs behind-traefik) + sotto-sezione modalità behind-Traefik con env vars richieste. Uso: docker compose -f docker-compose.prod.yml -f docker-compose.traefik.yml \ --env-file .env up -d Co-Authored-By: Claude Opus 4.7 (1M context) --- DEPLOYMENT.md | 55 ++++++++++++++++++++++++++++++++++ docker-compose.traefik.yml | 60 ++++++++++++++++++++++++++++++++++++++ gateway/Caddyfile | 9 +++++- scripts/deploy.sh | 20 ++++++++----- 4 files changed, 136 insertions(+), 8 deletions(-) create mode 100644 docker-compose.traefik.yml diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index fe0d4ad..fc0b148 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -62,6 +62,33 @@ 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. +## 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) Il modo più rapido è `scripts/deploy.sh`, idempotente. Esegui sul VPS: @@ -91,6 +118,34 @@ iniziale (testnet) → crea `/var/log/cerbero-mcp` con permessi `1000:1000` Per aggiornare in seguito: ri-esegui lo stesso script (preserva `.env`). +### 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 diff --git a/docker-compose.traefik.yml b/docker-compose.traefik.yml new file mode 100644 index 0000000..5f81303 --- /dev/null +++ b/docker-compose.traefik.yml @@ -0,0 +1,60 @@ +# 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" diff --git a/gateway/Caddyfile b/gateway/Caddyfile index 9ecf5ae..39bae28 100644 --- a/gateway/Caddyfile +++ b/gateway/Caddyfile @@ -1,12 +1,19 @@ { admin off email {$ACME_EMAIL:adrianodalpastro@tielogic.com} + auto_https {$AUTO_HTTPS:on} # Plugin mholt/caddy-ratelimit order rate_limit before basicauth + + # Trusted proxies: rispetta X-Forwarded-For quando dietro reverse proxy + # (es. Traefik). Default = solo private ranges. + servers { + trusted_proxies static {$TRUSTED_PROXIES:private_ranges} + } } -cerbero-mcp.tielogic.xyz { +{$LISTEN:cerbero-mcp.tielogic.xyz} { log { output stdout format json diff --git a/scripts/deploy.sh b/scripts/deploy.sh index 7bcdab7..eb16e35 100755 --- a/scripts/deploy.sh +++ b/scripts/deploy.sh @@ -135,16 +135,22 @@ echo "Audit log dir: $AUDIT_LOG_DIR (chown 1000:1000)" # ────────────────────────────────────────────────────────────── # 7. Pull image + up # ────────────────────────────────────────────────────────────── +COMPOSE_FILES=("-f" "docker-compose.prod.yml") +if [ "${BEHIND_TRAEFIK:-false}" = "true" ]; then + echo "=== Modalità behind-traefik attiva (network ${TRAEFIK_NETWORK:-gitea_traefik-public}) ===" + COMPOSE_FILES+=("-f" "docker-compose.traefik.yml") +fi + echo "=== docker compose pull + up ===" -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 "${COMPOSE_FILES[@]}" --env-file .env pull +docker compose "${COMPOSE_FILES[@]}" --env-file .env up -d # ────────────────────────────────────────────────────────────── # 8. Verifica stato # ────────────────────────────────────────────────────────────── sleep 5 echo "=== Stato container ===" -docker compose -f docker-compose.prod.yml --env-file .env ps +docker compose "${COMPOSE_FILES[@]}" --env-file .env ps echo echo "=== Smoke test (health check via gateway pubblico) ===" @@ -158,8 +164,8 @@ fi echo echo "=== Deploy completato ===" -echo "Comandi utili:" -echo " Logs: docker compose -f docker-compose.prod.yml --env-file .env logs -f " +echo "Comandi utili (compose files: ${COMPOSE_FILES[*]}):" +echo " Logs: docker compose ${COMPOSE_FILES[*]} --env-file .env logs -f " echo " Audit: tail -f $AUDIT_LOG_DIR/*.audit.jsonl" -echo " Restart: docker compose -f docker-compose.prod.yml --env-file .env restart " -echo " Stop: docker compose -f docker-compose.prod.yml --env-file .env down" +echo " Restart: docker compose ${COMPOSE_FILES[*]} --env-file .env restart " +echo " Stop: docker compose ${COMPOSE_FILES[*]} --env-file .env down"