feat(ci/cd): Gitea Actions + registry + Watchtower auto-update
ci / ruff lint (push) Failing after 1m37s
ci / mypy mcp_common (push) Has been cancelled
ci / pytest (push) Has been cancelled
ci / build & push to registry (push) Has been cancelled

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>
This commit is contained in:
AdrianoDev
2026-04-28 22:52:40 +02:00
parent 6b7b3f7658
commit c251fda886
4 changed files with 633 additions and 0 deletions
+204
View File
@@ -0,0 +1,204 @@
name: ci
on:
push:
branches: [main]
pull_request:
branches: [main]
env:
REGISTRY: git.tielogic.xyz
IMAGE_PREFIX: git.tielogic.xyz/adriano/cerbero-mcp
jobs:
lint:
name: ruff lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Cache uv
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
restore-keys: |
uv-${{ runner.os }}-
- name: Install deps
run: $HOME/.local/bin/uv sync --frozen --group dev
- name: Ruff check
run: $HOME/.local/bin/uv run ruff check services/
typecheck:
name: mypy mcp_common
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Cache uv
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
- name: Install deps
run: $HOME/.local/bin/uv sync --frozen --group dev
- name: Mypy on mcp_common (gating)
run: $HOME/.local/bin/uv run mypy services/common/src/mcp_common
- name: Mypy on services (warn-only)
run: $HOME/.local/bin/uv run mypy services/ || true
test:
name: pytest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
run: curl -LsSf https://astral.sh/uv/install.sh | sh
- name: Cache uv
uses: actions/cache@v4
with:
path: ~/.cache/uv
key: uv-${{ runner.os }}-${{ hashFiles('uv.lock') }}
- name: Install deps
run: $HOME/.local/bin/uv sync --frozen --group dev
- name: Pytest full suite
run: $HOME/.local/bin/uv run pytest services/ -v --tb=short
build-and-push:
name: build & push to registry
runs-on: ubuntu-latest
needs: [lint, test]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
packages: write
steps:
- uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Gitea registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ gitea.actor }}
password: ${{ secrets.GITEA_TOKEN }}
- name: Compute short SHA
id: meta
run: echo "sha=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Build base image (cache only, not pushed)
uses: docker/build-push-action@v6
with:
context: .
file: docker/base.Dockerfile
tags: cerbero-base:latest
load: true
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build & push gateway
uses: docker/build-push-action@v6
with:
context: ./gateway
file: gateway/Dockerfile
push: true
tags: |
${{ env.IMAGE_PREFIX }}/gateway:latest
${{ env.IMAGE_PREFIX }}/gateway:sha-${{ steps.meta.outputs.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build & push mcp-deribit
uses: docker/build-push-action@v6
with:
context: .
file: docker/mcp-deribit.Dockerfile
build-args: BASE_TAG=latest
push: true
tags: |
${{ env.IMAGE_PREFIX }}/mcp-deribit:latest
${{ env.IMAGE_PREFIX }}/mcp-deribit:sha-${{ steps.meta.outputs.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build & push mcp-bybit
uses: docker/build-push-action@v6
with:
context: .
file: docker/mcp-bybit.Dockerfile
build-args: BASE_TAG=latest
push: true
tags: |
${{ env.IMAGE_PREFIX }}/mcp-bybit:latest
${{ env.IMAGE_PREFIX }}/mcp-bybit:sha-${{ steps.meta.outputs.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build & push mcp-hyperliquid
uses: docker/build-push-action@v6
with:
context: .
file: docker/mcp-hyperliquid.Dockerfile
build-args: BASE_TAG=latest
push: true
tags: |
${{ env.IMAGE_PREFIX }}/mcp-hyperliquid:latest
${{ env.IMAGE_PREFIX }}/mcp-hyperliquid:sha-${{ steps.meta.outputs.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build & push mcp-alpaca
uses: docker/build-push-action@v6
with:
context: .
file: docker/mcp-alpaca.Dockerfile
build-args: BASE_TAG=latest
push: true
tags: |
${{ env.IMAGE_PREFIX }}/mcp-alpaca:latest
${{ env.IMAGE_PREFIX }}/mcp-alpaca:sha-${{ steps.meta.outputs.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build & push mcp-macro
uses: docker/build-push-action@v6
with:
context: .
file: docker/mcp-macro.Dockerfile
build-args: BASE_TAG=latest
push: true
tags: |
${{ env.IMAGE_PREFIX }}/mcp-macro:latest
${{ env.IMAGE_PREFIX }}/mcp-macro:sha-${{ steps.meta.outputs.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Build & push mcp-sentiment
uses: docker/build-push-action@v6
with:
context: .
file: docker/mcp-sentiment.Dockerfile
build-args: BASE_TAG=latest
push: true
tags: |
${{ env.IMAGE_PREFIX }}/mcp-sentiment:latest
${{ env.IMAGE_PREFIX }}/mcp-sentiment:sha-${{ steps.meta.outputs.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
+224
View File
@@ -0,0 +1,224 @@
# 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:
1. **lint** (`ruff check`) — gating
2. **typecheck** (`mypy mcp_common`) — gating su mcp_common, warn-only sui servizi
3. **test** (`pytest services/`) — gating, 455 test
4. **build-and-push** — solo su push a `main`:
- Logga al registry `git.tielogic.xyz` con `secrets.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)
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:
```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) Clona repository (solo per i file di compose, secret e Caddyfile)
```bash
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
```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 `/opt/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`.
## 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:
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.
## 4. 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.
## 5. 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
```
## 6. 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.
## 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:
```yaml
# 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:
```bash
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).
+6
View File
@@ -64,6 +64,12 @@ Vedi `secrets/*.json` e variabili `*_TESTNET` / `ALPACA_PAPER` in
### Deploy su VPS pubblica (`cerbero-mcp.tielogic.xyz`) ### Deploy su VPS pubblica (`cerbero-mcp.tielogic.xyz`)
Vedi [`DEPLOYMENT.md`](DEPLOYMENT.md) per la guida completa: pipeline CI/CD
(Gitea Actions → registry → Watchtower auto-update), setup VPS step-by-step,
rollback, smoke test post-deploy.
Il gateway Caddy è configurato per: Il gateway Caddy è configurato per:
- TLS automatico via Let's Encrypt (richiede DNS A/AAAA che punti al - TLS automatico via Let's Encrypt (richiede DNS A/AAAA che punti al
VPS e porte 80+443 raggiungibili). VPS e porte 80+443 raggiungibili).
+199
View File
@@ -0,0 +1,199 @@
# 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"
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
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
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
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
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}