From c251fda886bdf141ac95341462b51431c89eba4e Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Tue, 28 Apr 2026 22:52:40 +0200 Subject: [PATCH] feat(ci/cd): Gitea Actions + registry + Watchtower auto-update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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/: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) --- .gitea/workflows/ci.yml | 204 ++++++++++++++++++++++++++++++++++++ DEPLOYMENT.md | 224 ++++++++++++++++++++++++++++++++++++++++ README.md | 6 ++ docker-compose.prod.yml | 199 +++++++++++++++++++++++++++++++++++ 4 files changed, 633 insertions(+) create mode 100644 .gitea/workflows/ci.yml create mode 100644 DEPLOYMENT.md create mode 100644 docker-compose.prod.yml diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..cacd193 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -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 diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..92032ae --- /dev/null +++ b/DEPLOYMENT.md @@ -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 --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 | 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). diff --git a/README.md b/README.md index 3ea38d4..35884b6 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,12 @@ Vedi `secrets/*.json` e variabili `*_TESTNET` / `ALPACA_PAPER` in ### 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: - TLS automatico via Let's Encrypt (richiede DNS A/AAAA che punti al VPS e porte 80+443 raggiungibili). diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..26de576 --- /dev/null +++ b/docker-compose.prod.yml @@ -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}