# Local Hold Slayer stack: the single app image + its own PostgreSQL. # # Hold Slayer is ONE FastAPI process exposing REST/WS/MCP and serving its built # SvelteKit dashboard at "/" — no separate web/nginx service (the Dockerfile's # node stage builds the dashboard into the image). # # Auth is Casdoor SSO (owner-only). Because the published port binds the app to # 0.0.0.0, dev-owner mode (CASDOOR_ENABLED=false) is intentionally REFUSED at # startup here — that mode is loopback-only. So the stack expects the CASDOOR_* # + OWNER_NAME vars set (see .env.compose.example). MCP/CLI clients then use an # owner-minted PAT. # # cp .env.compose.example .env # # fill in CASDOOR_* + OWNER_NAME (+ HS_DB_PASSWORD) # docker compose up --build services: db: image: postgres:17 environment: POSTGRES_USER: ${HS_DB_USER:-holdslayer} POSTGRES_PASSWORD: ${HS_DB_PASSWORD:?set HS_DB_PASSWORD in .env} POSTGRES_DB: ${HS_DB_NAME:-holdslayer} volumes: - hs_pgdata:/var/lib/postgresql/data # json-file + Alloy docker-socket discovery is the estate pattern; no # syslog driver / 514xx listener (which would block container creation when # the listener is absent). See ouranos Rosalind/Virgo logging convention. logging: driver: json-file options: max-size: "10m" max-file: "3" healthcheck: test: ["CMD-SHELL", "pg_isready -U ${HS_DB_USER:-holdslayer} -d ${HS_DB_NAME:-holdslayer}"] interval: 10s timeout: 5s retries: 5 restart: unless-stopped app: build: . depends_on: db: condition: service_healthy environment: # Migrations run in the app's own init_db() on boot; it just needs to # reach the db service. asyncpg URL points at the compose service name. DATABASE_URL: postgresql+asyncpg://${HS_DB_USER:-holdslayer}:${HS_DB_PASSWORD}@db:5432/${HS_DB_NAME:-holdslayer} HOST: "0.0.0.0" PORT: "21081" # Mock SIP: this stack is a dev/local deploy, not a real trunk. /health # honestly reports engine=mock as "degraded". Flip to false + fill the # SIP_TRUNK_* vars for a real-trunk deploy. USE_MOCK_SIP: ${USE_MOCK_SIP:-true} # --- Auth: Casdoor SSO (owner-only) --- CASDOOR_ENABLED: ${CASDOOR_ENABLED:-true} CASDOOR_ENDPOINT: ${CASDOOR_ENDPOINT:-https://id.ouranos.helu.ca} CASDOOR_CLIENT_ID: ${CASDOOR_CLIENT_ID} CASDOOR_CLIENT_SECRET: ${CASDOOR_CLIENT_SECRET} CASDOOR_ORG_NAME: ${CASDOOR_ORG_NAME:-heluca} CASDOOR_APP_NAME: ${CASDOOR_APP_NAME:-hold-slayer} OWNER_NAME: ${OWNER_NAME:?set OWNER_NAME (the owner's Casdoor username)} PUBLIC_BASE_URL: ${PUBLIC_BASE_URL:-} ports: - "${HS_APP_PORT:-21081}:21081" logging: driver: json-file options: max-size: "10m" max-file: "3" healthcheck: # /health returns 200 even when "degraded" (mock engine / unregistered # trunk) — so a 200 means the process is up and serving, which is the # right liveness signal for a mock-SIP dev stack. test: ["CMD", "curl", "-f", "http://localhost:21081/health"] interval: 30s timeout: 10s retries: 3 start_period: 40s restart: unless-stopped volumes: hs_pgdata: