101 lines
3.9 KiB
YAML
101 lines
3.9 KiB
YAML
# Cyclone — local docker-compose stack.
|
|
#
|
|
# Two services on a user-defined network:
|
|
#
|
|
# frontend (nginx) — published on http://127.0.0.1:8080
|
|
# serves the built SPA + reverse-proxies /api/* to backend
|
|
# backend (uvicorn) — NOT published externally by default; reachable from
|
|
# the frontend over the compose network on backend:8000
|
|
# (override `ports:` below to expose it for curl/debug)
|
|
#
|
|
# Persistent state:
|
|
# - `cyclone-data` named volume mounted at /data in the backend holds
|
|
# the SQLite database file. Survives `docker compose down`; only
|
|
# `docker compose down -v` wipes it.
|
|
# - `config/payers.yaml` is baked into the backend image at build time.
|
|
# To edit payer config, change the YAML, rebuild the backend image,
|
|
# and `POST /api/admin/reload-config` (or just restart the container).
|
|
#
|
|
# Usage:
|
|
# docker compose up -d --build # start (or rebuild + restart)
|
|
# docker compose logs -f # tail both services
|
|
# docker compose restart backend # bounce the backend (e.g. after crash)
|
|
# docker compose down # stop containers, KEEP the data volume
|
|
# docker compose down -v # stop AND wipe the data volume
|
|
|
|
name: cyclone
|
|
|
|
services:
|
|
backend:
|
|
build:
|
|
context: .
|
|
dockerfile: backend/Dockerfile
|
|
image: cyclone-backend:local
|
|
container_name: cyclone-backend
|
|
restart: unless-stopped
|
|
environment:
|
|
# Bind on all interfaces inside the container (required — 127.0.0.1
|
|
# would only be reachable from inside the container itself).
|
|
CYCLONE_HOST: "0.0.0.0"
|
|
CYCLONE_PORT: "8000"
|
|
CYCLONE_RELOAD: "0"
|
|
# Absolute path inside the container; the named volume mounts at /data.
|
|
CYCLONE_DB_URL: "sqlite:////data/cyclone.db"
|
|
# Bootstrap admin (required on first boot unless at least one user
|
|
# already exists). The ${VAR:?msg} syntax makes docker-compose refuse
|
|
# to start with a clear error if the env var isn't set in the host
|
|
# environment.
|
|
CYCLONE_ADMIN_USERNAME: ${CYCLONE_ADMIN_USERNAME:?CYCLONE_ADMIN_USERNAME is required on first boot}
|
|
CYCLONE_ADMIN_PASSWORD: ${CYCLONE_ADMIN_PASSWORD:?CYCLONE_ADMIN_PASSWORD is required on first boot (min 12 chars)}
|
|
volumes:
|
|
- cyclone-data:/data
|
|
# The healthcheck in the Dockerfile hits /api/health. The frontend
|
|
# depends_on `service_healthy` so it won't accept traffic until the
|
|
# backend is responsive.
|
|
healthcheck:
|
|
test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8000/api/health"]
|
|
interval: 15s
|
|
timeout: 5s
|
|
retries: 3
|
|
start_period: 10s
|
|
# No `ports:` — the frontend reaches the backend over the compose
|
|
# network. Uncomment the next line to expose the API directly for
|
|
# `curl http://localhost:8000/api/...` from the host.
|
|
# ports:
|
|
# - "127.0.0.1:8000:8000"
|
|
networks:
|
|
- cyclone-net
|
|
|
|
frontend:
|
|
build:
|
|
context: .
|
|
dockerfile: frontend/Dockerfile
|
|
image: cyclone-frontend:local
|
|
container_name: cyclone-frontend
|
|
restart: unless-stopped
|
|
depends_on:
|
|
backend:
|
|
condition: service_healthy
|
|
ports:
|
|
# Bind address defaults to 0.0.0.0 (reachable from the LAN). Set
|
|
# CYCLONE_BIND_ADDRESS=127.0.0.1 to tighten to loopback-only and
|
|
# match the standalone install's local-only posture.
|
|
# Port defaults to 8081 to dodge the common clash on 8080; override
|
|
# with CYCLONE_WEB_PORT=... (see README).
|
|
- "${CYCLONE_BIND_ADDRESS:-0.0.0.0}:${CYCLONE_WEB_PORT:-8081}:80"
|
|
healthcheck:
|
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://127.0.0.1/healthz"]
|
|
interval: 15s
|
|
timeout: 3s
|
|
retries: 3
|
|
start_period: 5s
|
|
networks:
|
|
- cyclone-net
|
|
|
|
networks:
|
|
cyclone-net:
|
|
driver: bridge
|
|
|
|
volumes:
|
|
cyclone-data:
|
|
name: cyclone-data |