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:
@@ -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/
|
||||||
@@ -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
@@ -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.
|
||||||
@@ -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
|
||||||
Reference in New Issue
Block a user