feat(sp23): docker-compose.yml with backend + frontend, named volumes, secrets
This commit is contained in:
@@ -0,0 +1,13 @@
|
|||||||
|
# Repo-root .dockerignore — applies to `docker compose build` (which builds
|
||||||
|
# both backend and frontend contexts from the repo root). Excludes anything
|
||||||
|
# that should never end up in a build context.
|
||||||
|
|
||||||
|
.worktrees/
|
||||||
|
.git/
|
||||||
|
.github/
|
||||||
|
docs/prodfiles/
|
||||||
|
*.production.txt
|
||||||
|
node_modules/
|
||||||
|
dist/
|
||||||
|
.venv/
|
||||||
|
.superpowers/brainstorm/
|
||||||
+75
-78
@@ -1,101 +1,98 @@
|
|||||||
# 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
|
name: cyclone
|
||||||
|
|
||||||
|
# Two-container production stack for Cyclone.
|
||||||
|
#
|
||||||
|
# backend FastAPI on python:3.11-slim-bookworm, internal port 8000.
|
||||||
|
# frontend nginx:1.27-alpine serving the built React SPA + reverse-
|
||||||
|
# proxying /api/* to the backend, host port 8080.
|
||||||
|
#
|
||||||
|
# Tag strategy:
|
||||||
|
# - Images are tagged semver from package.json / pyproject.toml.
|
||||||
|
# - Two aliases per image: `:latest` (set automatically by `docker
|
||||||
|
# compose build`) and `:stable` (manually promoted after a release
|
||||||
|
# has been running in prod for >=1 week).
|
||||||
|
# - Compose defaults to `:stable` so a fresh `docker compose build`
|
||||||
|
# never auto-rolls a new release.
|
||||||
|
# - Override with `TAG=0.0.9 docker compose up -d` to roll back.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
backend:
|
backend:
|
||||||
|
image: cyclone-backend:${TAG:-stable}
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ./backend
|
||||||
dockerfile: backend/Dockerfile
|
|
||||||
image: cyclone-backend:local
|
|
||||||
container_name: cyclone-backend
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
# Bind on all interfaces inside the container (required — 127.0.0.1
|
# SQLite path lives on the named `cyclone_db` volume.
|
||||||
# would only be reachable from inside the container itself).
|
CYCLONE_DB_URL: "sqlite:////var/lib/cyclone/db/cyclone.db"
|
||||||
CYCLONE_HOST: "0.0.0.0"
|
# Wire the existing BackupService (SP17) to autostart on container boot.
|
||||||
CYCLONE_PORT: "8000"
|
CYCLONE_BACKUP_AUTOSTART: "1"
|
||||||
CYCLONE_RELOAD: "0"
|
CYCLONE_BACKUP_INTERVAL_HOURS: "24"
|
||||||
# Absolute path inside the container; the named volume mounts at /data.
|
CYCLONE_BACKUP_RETENTION_DAYS: "14"
|
||||||
CYCLONE_DB_URL: "sqlite:////data/cyclone.db"
|
# Logging — JSON to stdout (collected by `docker logs`) and to the
|
||||||
# Bootstrap admin (required on first boot unless at least one user
|
# bind-mounted file (collected by host-side logrotate).
|
||||||
# already exists). The ${VAR:?msg} syntax makes docker-compose refuse
|
CYCLONE_LOG_LEVEL: "INFO"
|
||||||
# to start with a clear error if the env var isn't set in the host
|
CYCLONE_LOG_FILE: "/var/log/cyclone/cyclone.log"
|
||||||
# environment.
|
CYCLONE_LOG_JSON: "1"
|
||||||
CYCLONE_ADMIN_USERNAME: ${CYCLONE_ADMIN_USERNAME:?CYCLONE_ADMIN_USERNAME is required on first boot}
|
# Cookie signing key defaults to the SQLCipher key. Override in
|
||||||
CYCLONE_ADMIN_PASSWORD: ${CYCLONE_ADMIN_PASSWORD:?CYCLONE_ADMIN_PASSWORD is required on first boot (min 12 chars)}
|
# production if you want to rotate them independently.
|
||||||
|
CYCLONE_SECRET_KEY_FILE: "/run/secrets/cyclone_db_key"
|
||||||
|
CYCLONE_COOKIE_SECURE: "1"
|
||||||
|
# First-admin bootstrap — populated by scripts/cyclone-init.sh.
|
||||||
|
# The `_FILE` variants are the standard Docker-secret pattern:
|
||||||
|
# the env var points at the mounted secret file rather than
|
||||||
|
# embedding the secret in the compose file.
|
||||||
|
CYCLONE_ADMIN_USERNAME_FILE: "/run/secrets/cyclone_admin_username"
|
||||||
|
CYCLONE_ADMIN_PASSWORD_FILE: "/run/secrets/cyclone_admin_password"
|
||||||
|
secrets:
|
||||||
|
- cyclone_db_key
|
||||||
|
- cyclone_admin_username
|
||||||
|
- cyclone_admin_password
|
||||||
volumes:
|
volumes:
|
||||||
- cyclone-data:/data
|
- cyclone_db:/var/lib/cyclone/db
|
||||||
# The healthcheck in the Dockerfile hits /api/health. The frontend
|
- cyclone_backups:/var/lib/cyclone/backups
|
||||||
# depends_on `service_healthy` so it won't accept traffic until the
|
- cyclone_prodfiles:/var/lib/cyclone/prodfiles
|
||||||
# backend is responsive.
|
- cyclone_sftp_staging:/var/lib/cyclone/sftp_staging
|
||||||
|
- cyclone_logs:/var/log/cyclone
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "--fail", "--silent", "http://127.0.0.1:8000/api/health"]
|
test: ["CMD", "curl", "-fs", "http://localhost:8000/api/healthz"]
|
||||||
interval: 15s
|
interval: 30s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
start_period: 10s
|
start_period: 30s
|
||||||
# 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:
|
networks:
|
||||||
- cyclone-net
|
- cyclone_network
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
|
image: cyclone-frontend:${TAG:-stable}
|
||||||
build:
|
build:
|
||||||
context: .
|
context: ./frontend
|
||||||
dockerfile: frontend/Dockerfile
|
|
||||||
image: cyclone-frontend:local
|
|
||||||
container_name: cyclone-frontend
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
# LAN-bind: only this port is published to the host. The operator
|
||||||
|
# is expected to firewall this to the LAN subnet (or rely on VPN
|
||||||
|
# for outside access). NO public TLS in v1.
|
||||||
|
- "8080:8080"
|
||||||
depends_on:
|
depends_on:
|
||||||
backend:
|
backend:
|
||||||
condition: service_healthy
|
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:
|
networks:
|
||||||
- cyclone-net
|
- cyclone_network
|
||||||
|
|
||||||
networks:
|
secrets:
|
||||||
cyclone-net:
|
cyclone_db_key:
|
||||||
driver: bridge
|
file: /etc/cyclone/secrets/db.key
|
||||||
|
cyclone_admin_username:
|
||||||
|
file: /etc/cyclone/secrets/admin_username
|
||||||
|
cyclone_admin_password:
|
||||||
|
file: /etc/cyclone/secrets/admin_pw
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
cyclone-data:
|
cyclone_db:
|
||||||
name: cyclone-data
|
cyclone_backups:
|
||||||
|
cyclone_prodfiles:
|
||||||
|
cyclone_sftp_staging:
|
||||||
|
cyclone_logs:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
cyclone_network:
|
||||||
|
driver: bridge
|
||||||
|
|||||||
Reference in New Issue
Block a user