feat(ci/cd): Gitea Actions + registry + Watchtower auto-update
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:
@@ -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
@@ -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).
|
||||
@@ -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).
|
||||
|
||||
@@ -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}
|
||||
Reference in New Issue
Block a user