Files
momentum-system/docker-compose.yml
T
momentum devandClaude Fable 5 cf431c6c2b delete: standalone grafana/ stack, telemetry/ husk, dead workflow, stale uv.lock
grafana/ pointed at the decommissioned pre-migration DB with committed dev
credentials (rotation note in plans/002-deletions.log); its dashboards
provisioning MOVED to deploy/compose/grafana/dashboards (docker-compose.yml
bind-mount re-pointed — the in-compose grafana service keeps working).
docker-publish.yml fired on a nonexistent branch pushing a wrong image name.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 21:02:46 -04:00

222 lines
7.1 KiB
YAML

# Full self-contained stack for the Momentum System.
#
# docker compose up -d # postgres + loop + dashboard + qabot + transcripts + scheduler
# docker compose --profile monitoring up -d # also start Grafana
# docker compose run --rm nightly-tune # run a job on demand
#
# Secrets come from .env (never baked into the image). DATABASE_URL is
# overridden below to the in-stack postgres so the stack is self-contained —
# migrating from the LAN Postgres (192.168.1.5) is a pg_dump/restore into the
# pg-data volume; see deploy/docker/README.md. To run the app services against
# the LAN Postgres instead (transition mode), comment out the DATABASE_URL
# override and let .env's value through.
#
# TZ is America/New_York in the image: the loop's market clock thinks in ET.
x-app: &app
build: .
image: momentum-app:latest
env_file:
- path: .env
required: false
volumes:
- app-data:/app/data
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
logging:
driver: json-file
options: { max-size: "20m", max-file: "3" }
x-app-env: &app-env
# Self-contained default: the in-stack postgres. Overrides .env.
DATABASE_URL: postgresql://${POSTGRES_USER:-momentum}:${POSTGRES_PASSWORD:-momentum}@postgres:5432/${POSTGRES_DB:-momentum}
PG_SKIP_SCHEMA: "0"
services:
postgres:
image: postgres:18
environment:
POSTGRES_USER: ${POSTGRES_USER:-momentum}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-momentum}
POSTGRES_DB: ${POSTGRES_DB:-momentum}
PGDATA: /var/lib/postgresql/data/pgdata
volumes:
- pg-data:/var/lib/postgresql/data
ports:
- "5432:5432" # psql/Grafana-on-host debugging; remove if not needed
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-momentum} -d ${POSTGRES_DB:-momentum}"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
loop:
<<: *app
container_name: momentum-loop
command: ["python", "-u", "run_live_paper.py", "--no-dashboard"]
environment:
<<: *app-env
healthcheck:
# heartbeat: the loop must have written SOME event in the last 5 minutes
# (module ticks flow around the clock, not just market hours)
# PG_SKIP_SCHEMA=1: a healthcheck must NOT re-apply schema DDL — the 60s
# CREATE-IF-NOT-EXISTS lock burst cancelled minute_bars autovacuum forever
# (2026-07-09 post-migration: stats rotted, dashboard queries crawled)
test: ["CMD", "python", "-c", "import os; os.environ['PG_SKIP_SCHEMA']='1'; import datetime,sys; from research.multi_schema import open_research_db; r=open_research_db('market').execute('SELECT max(timestamp) FROM events').fetchall()[0][0]; sys.exit(0 if r and (datetime.datetime.now()-r).total_seconds()<300 else 1)"]
interval: 60s
timeout: 20s
retries: 5
start_period: 180s
dashboard:
<<: *app
container_name: momentum-dashboard
command: ["python", "-u", "dashboard_api.py"]
environment:
<<: *app-env
DASHBOARD_HOST: "0.0.0.0"
DASHBOARD_PORT: "8765"
ports:
- "8765:8765"
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:8765/"]
interval: 30s
timeout: 10s
retries: 3
start_period: 30s
qabot:
<<: *app
container_name: momentum-qabot
command: ["python", "-u", "telegram_bot.py"]
environment:
<<: *app-env
transcripts:
<<: *app
container_name: momentum-transcripts
command: ["python", "-u", "-m", "youtube_claims.run", "run"]
environment:
<<: *app-env
admin:
<<: *app
container_name: momentum-admin
command: ["python", "-u", "admin_api.py"]
environment:
<<: *app-env
ADMIN_PORT: "8767"
# set ADMIN_TOKEN in .env — without it the panel is open on the LAN
ports:
- "8767:8767"
volumes:
- app-data:/app/data
- /var/run/docker.sock:/var/run/docker.sock # service status + restarts
scheduler:
# Weekday-evening premarket eval + DB/log housekeeping at 20:00 ET —
# replaces the momentum-premarket-eval systemd timer.
<<: *app
container_name: momentum-scheduler
environment:
<<: *app-env
command:
- /bin/sh
- -c
- |
echo "[scheduler] up; will fire 20:00 ET Mon-Fri"
while true; do
if [ "$$(date +%H:%M)" = "20:00" ] && [ "$$(date +%u)" -le 5 ]; then
echo "[scheduler] premarket_eval $$(date)"
python -m research.premarket_eval || echo "[scheduler] eval FAILED"
sleep 120
fi
sleep 20
done
db-backup:
# Nightly pg_dump at 20:10 ET Mon-Fri (after the 20:00 scheduler pass).
# Runs on the postgres:18 image — a version-matched pg_dump for free; the
# app image has none. Backups land in the app-data volume under backups/.
image: postgres:18
container_name: momentum-db-backup
restart: unless-stopped
environment:
# in-stack postgres, same value the app services use (NOT .env's
# DATABASE_URL, which may point at the old LAN instance)
DATABASE_URL: postgresql://${POSTGRES_USER:-momentum}:${POSTGRES_PASSWORD:-momentum}@postgres:5432/${POSTGRES_DB:-momentum}
BACKUP_DIR: /data/backups
BACKUP_KEEP_DAYS: "14"
TZ: America/New_York # the postgres image defaults to UTC; the 20:10 gate is ET
volumes:
- ./deploy/docker/backup_db.sh:/backup_db.sh:ro
- app-data:/data
depends_on:
postgres:
condition: service_healthy
command:
- /bin/bash
- -c
- |
echo "[backup] up; fires 20:10 ET Mon-Fri"
while true; do
if [ "$$(date +%H:%M)" = "20:10" ] && [ "$$(date +%u)" -le 5 ]; then
bash /backup_db.sh || echo "[backup] FAILED"
sleep 120
fi
sleep 20
done
# ---- On-demand jobs: docker compose run --rm <name>
journal:
<<: *app
profiles: ["jobs"]
command: ["python", "momentum_cli.py", "journal"]
environment: *app-env
restart: "no"
eod-replay:
<<: *app
profiles: ["jobs"]
command: ["python", "eod_replay.py"]
environment: *app-env
restart: "no"
nightly-tune:
<<: *app
profiles: ["jobs"]
command: ["python", "nightly_tune.py"]
environment: *app-env
restart: "no"
# ---- Grafana (optional): docker compose --profile monitoring up -d
grafana:
image: grafana/grafana:11.2.0
profiles: ["monitoring"]
environment:
GF_AUTH_ANONYMOUS_ENABLED: "true"
GF_AUTH_ANONYMOUS_ORG_ROLE: "Admin"
GF_AUTH_DISABLE_LOGIN_FORM: "true"
GF_DASHBOARDS_MIN_REFRESH_INTERVAL: "2s"
PG_USER: ${POSTGRES_USER:-momentum}
PG_PASSWORD: ${POSTGRES_PASSWORD:-momentum}
PG_DATABASE: ${POSTGRES_DB:-momentum}
ports:
- "3000:3000"
volumes:
- ./deploy/compose/grafana/datasources:/etc/grafana/provisioning/datasources:ro
- ./deploy/compose/grafana/dashboards:/etc/grafana/provisioning/dashboards:ro
- grafana-data:/var/lib/grafana
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
volumes:
pg-data:
app-data:
grafana-data: