feat(deploy): docker-compose.traefik.yml overlay per behind-Traefik
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) <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,33 @@ Settings → Applications → Generate Token). Aggiungilo come secret a
|
|||||||
livello user (User Settings → Secrets → New Secret) con nome
|
livello user (User Settings → Secrets → New Secret) con nome
|
||||||
`REGISTRY_TOKEN`. Tutti i tuoi repo ereditano il secret automaticamente.
|
`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)
|
## 2. Deploy automatizzato (script)
|
||||||
|
|
||||||
Il modo più rapido è `scripts/deploy.sh`, idempotente. Esegui sul VPS:
|
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`).
|
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
|
## 3. Safety: switch testnet → mainnet
|
||||||
|
|
||||||
`mcp_common.environment.consistency_check` (richiamato dal boot
|
`mcp_common.environment.consistency_check` (richiamato dal boot
|
||||||
|
|||||||
@@ -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"
|
||||||
+8
-1
@@ -1,12 +1,19 @@
|
|||||||
{
|
{
|
||||||
admin off
|
admin off
|
||||||
email {$ACME_EMAIL:adrianodalpastro@tielogic.com}
|
email {$ACME_EMAIL:adrianodalpastro@tielogic.com}
|
||||||
|
auto_https {$AUTO_HTTPS:on}
|
||||||
|
|
||||||
# Plugin mholt/caddy-ratelimit
|
# Plugin mholt/caddy-ratelimit
|
||||||
order rate_limit before basicauth
|
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 {
|
log {
|
||||||
output stdout
|
output stdout
|
||||||
format json
|
format json
|
||||||
|
|||||||
+13
-7
@@ -135,16 +135,22 @@ echo "Audit log dir: $AUDIT_LOG_DIR (chown 1000:1000)"
|
|||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
# 7. Pull image + up
|
# 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 ==="
|
echo "=== docker compose pull + up ==="
|
||||||
docker compose -f docker-compose.prod.yml --env-file .env pull
|
docker compose "${COMPOSE_FILES[@]}" --env-file .env pull
|
||||||
docker compose -f docker-compose.prod.yml --env-file .env up -d
|
docker compose "${COMPOSE_FILES[@]}" --env-file .env up -d
|
||||||
|
|
||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
# 8. Verifica stato
|
# 8. Verifica stato
|
||||||
# ──────────────────────────────────────────────────────────────
|
# ──────────────────────────────────────────────────────────────
|
||||||
sleep 5
|
sleep 5
|
||||||
echo "=== Stato container ==="
|
echo "=== Stato container ==="
|
||||||
docker compose -f docker-compose.prod.yml --env-file .env ps
|
docker compose "${COMPOSE_FILES[@]}" --env-file .env ps
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=== Smoke test (health check via gateway pubblico) ==="
|
echo "=== Smoke test (health check via gateway pubblico) ==="
|
||||||
@@ -158,8 +164,8 @@ fi
|
|||||||
|
|
||||||
echo
|
echo
|
||||||
echo "=== Deploy completato ==="
|
echo "=== Deploy completato ==="
|
||||||
echo "Comandi utili:"
|
echo "Comandi utili (compose files: ${COMPOSE_FILES[*]}):"
|
||||||
echo " Logs: docker compose -f docker-compose.prod.yml --env-file .env logs -f <service>"
|
echo " Logs: docker compose ${COMPOSE_FILES[*]} --env-file .env logs -f <service>"
|
||||||
echo " Audit: tail -f $AUDIT_LOG_DIR/*.audit.jsonl"
|
echo " Audit: tail -f $AUDIT_LOG_DIR/*.audit.jsonl"
|
||||||
echo " Restart: docker compose -f docker-compose.prod.yml --env-file .env restart <service>"
|
echo " Restart: docker compose ${COMPOSE_FILES[*]} --env-file .env restart <service>"
|
||||||
echo " Stop: docker compose -f docker-compose.prod.yml --env-file .env down"
|
echo " Stop: docker compose ${COMPOSE_FILES[*]} --env-file .env down"
|
||||||
|
|||||||
Reference in New Issue
Block a user