feat(docker): deploy su VPS via traefik con cerbero-mcp interno

Dockerfile multi-stage (python:3.13-slim + uv) e docker-compose con due
servizi che condividono l'immagine:

- multi-swarm-paper: runner long-running scripts/run_paper_trading.py
- multi-swarm-dashboard: Streamlit su https://swarm.${DOMAIN_NAME}

Cerbero raggiunto via rete docker interna (http://cerbero-mcp:9000)
saltando il giro pubblico traefik+TLS. Persistenza via bind mount su
data/, series/, state/ (runs.db con WAL), strategies/ in read-only.

.env.example aggiornato con DOMAIN_NAME, SWARM_DASHBOARD_PORT (porta
interna parametrizzabile) e i PAPER_* per override del command.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-05-14 10:21:10 +00:00
parent 45f273f591
commit 8e5efde219
4 changed files with 233 additions and 0 deletions
+58
View File
@@ -0,0 +1,58 @@
# Git
.git
.gitignore
.gitattributes
# Python caches
__pycache__
*.py[cod]
*.egg-info
.venv
venv
.pytest_cache
.mypy_cache
.ruff_cache
# Editors / OS
.vscode
.idea
.DS_Store
*.swp
*.swo
# Secrets — montati via env_file nel compose, mai dentro l'immagine
.env
.env.*
!.env.example
*.pem
*.key
# Artefatti runtime — vivono come bind mount, non nell'immagine
runs.db
runs.db-journal
runs.db-wal
runs.db-shm
data/
series/
state/
*.parquet
*.feather
checkpoints/
logs/
*.log
# Build / dist
build/
dist/
# Docs grandi — non servono in immagine
docs/
*.md
!README.md
# Test — non servono in runtime (l'immagine non gira pytest)
tests/
# OMC / claude metadata
.omc/
.claude/
+13
View File
@@ -22,3 +22,16 @@ RUN_NAME=phase1-spike-001
DATA_DIR=./data DATA_DIR=./data
SERIES_DIR=./series SERIES_DIR=./series
DB_PATH=./runs.db DB_PATH=./runs.db
# Docker / Traefik (usati SOLO da docker-compose.yml)
# Dominio base: traefik espone la dashboard su swarm.${DOMAIN_NAME}
DOMAIN_NAME=tielogic.xyz
# Porta interna della Streamlit dashboard (Traefik fa il TLS davanti)
SWARM_DASHBOARD_PORT=8501
# Paper-trading runner — override del command nel compose (opzionali)
PAPER_RUN_NAME=phase3-papertrade-prod
PAPER_INITIAL_CAPITAL=1000
PAPER_FEES_BP=5.0
PAPER_POLL_SECONDS=300
PAPER_LOOKBACK_BARS=500
+52
View File
@@ -0,0 +1,52 @@
# syntax=docker/dockerfile:1.7
#
# Multi-Swarm Coevolutive — immagine unica usata da due servizi:
# * paper-trading runner (scripts/run_paper_trading.py)
# * Streamlit dashboard (src/multi_swarm/dashboard/streamlit_app.py)
#
# Builder stage: risolve uv.lock con `uv sync --frozen --no-dev` e produce
# un venv in /app/.venv. Runtime stage: copia solo /app + scripts/ e gira
# come utente non-root. data/, series/, strategies/, state/ sono bind
# mount dal compose, quindi non finiscono nell'immagine.
FROM python:3.13-slim AS builder
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential curl \
&& rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir "uv>=0.5,<0.9"
WORKDIR /app
COPY pyproject.toml uv.lock README.md ./
COPY src ./src
RUN uv sync --frozen --no-dev
FROM python:3.13-slim AS runtime
LABEL org.opencontainers.image.title="multi-swarm" \
org.opencontainers.image.version="0.1.0" \
org.opencontainers.image.source="https://git.tielogic.xyz/Adriano/Multi_Swarm_Coevolutive"
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /app /app
COPY scripts ./scripts
ENV PATH="/app/.venv/bin:$PATH" \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH=/app/src
RUN useradd -m -u 1000 app \
&& mkdir -p /app/data /app/series /app/state /app/strategies \
&& chown -R app:app /app
USER app
# Healthcheck di default: import del package — i servizi reali lo
# sovrascrivono nel compose (streamlit /_stcore/health).
HEALTHCHECK --interval=60s --timeout=5s --retries=3 --start-period=10s \
CMD python -c "import multi_swarm" || exit 1
# Nessun CMD di default: il compose specifica entrypoint/command
# per ognuno dei due servizi.
+110
View File
@@ -0,0 +1,110 @@
# docker-compose.yml — Multi-Swarm Coevolutive
#
# Due servizi che condividono la stessa immagine `multi-swarm:dev`:
#
# * multi-swarm-paper — paper-trading runner long-running
# (scripts/run_paper_trading.py)
# * multi-swarm-dashboard — Streamlit dashboard esposta da Traefik
# su https://swarm.${DOMAIN_NAME:-tielogic.xyz}
#
# Entrambi joinano la rete external `traefik` cosi' il client Cerbero
# risolve direttamente l'host `cerbero-mcp` (porta 9000) senza passare
# dal gateway pubblico ne' dal TLS.
#
# Dati persistenti via bind mount dalla cartella del repo:
# ./data cache OHLCV intermedia
# ./series cache parquet per timeframe/symbol
# ./state contiene runs.db (+ WAL/SHM)
# ./strategies btc_*.json / eth_*.json letti dal paper runner
#
# Secrets (token Cerbero + OpenRouter): caricati da .env via env_file.
# Le variabili sotto `environment:` sovrascrivono solo i valori che
# devono cambiare dentro il container (URL interno, path container).
networks:
traefik:
external: true
x-swarm-env: &swarm-env
# Override: rotta interna verso cerbero-mcp (no TLS, no traefik hop)
CERBERO_BASE_URL: http://cerbero-mcp:9000
# Override: path container per persistenza
DATA_DIR: /app/data
SERIES_DIR: /app/series
DB_PATH: /app/state/runs.db
services:
multi-swarm-paper:
build:
context: .
dockerfile: Dockerfile
image: multi-swarm:dev
container_name: multi-swarm-paper
restart: unless-stopped
networks: [traefik]
env_file: .env
environment:
<<: *swarm-env
volumes:
- ./data:/app/data
- ./series:/app/series
- ./state:/app/state
- ./strategies:/app/strategies:ro
# Niente HTTP da controllare: ci affidiamo a `restart: unless-stopped`
# e ai log per la liveness del runner.
command:
- python
- /app/scripts/run_paper_trading.py
- --name=${PAPER_RUN_NAME:-phase3-papertrade-prod}
- --initial-capital=${PAPER_INITIAL_CAPITAL:-1000}
- --fees-bp=${PAPER_FEES_BP:-5.0}
- --poll-seconds=${PAPER_POLL_SECONDS:-300}
- --lookback-bars=${PAPER_LOOKBACK_BARS:-500}
- --strategies-dir=/app/strategies
labels:
- com.centurylinklabs.watchtower.enable=true
multi-swarm-dashboard:
image: multi-swarm:dev
build:
context: .
dockerfile: Dockerfile
container_name: multi-swarm-dashboard
restart: unless-stopped
networks: [traefik]
env_file: .env
environment:
<<: *swarm-env
volumes:
# Dashboard legge solo runs.db: mount in read-only
- ./state:/app/state:ro
- ./data:/app/data:ro
- ./series:/app/series:ro
entrypoint:
- streamlit
- run
- /app/src/multi_swarm/dashboard/streamlit_app.py
- --server.address=0.0.0.0
- --server.port=${SWARM_DASHBOARD_PORT:-8501}
- --server.headless=true
- --browser.gatherUsageStats=false
command: []
healthcheck:
test:
- "CMD"
- "python"
- "-c"
- "import os, urllib.request; urllib.request.urlopen(f'http://localhost:{os.environ.get(\"SWARM_DASHBOARD_PORT\",\"8501\")}/_stcore/health', timeout=3).close()"
interval: 30s
timeout: 5s
retries: 3
start_period: 30s
labels:
- traefik.enable=true
- traefik.docker.network=traefik
- "traefik.http.routers.multi-swarm-dashboard.rule=Host(`swarm.${DOMAIN_NAME:-tielogic.xyz}`)"
- traefik.http.routers.multi-swarm-dashboard.tls=true
- traefik.http.routers.multi-swarm-dashboard.entrypoints=websecure
- traefik.http.routers.multi-swarm-dashboard.tls.certresolver=mytlschallenge
- "traefik.http.services.multi-swarm-dashboard.loadbalancer.server.port=${SWARM_DASHBOARD_PORT:-8501}"
- com.centurylinklabs.watchtower.enable=true