- Dockerfile: python:3.12-slim, non-root app user, gunicorn wsgi:application, HEALTHCHECK on the public /health route. - Single-writer ledger safety: exactly ONE gunicorn worker (SQLite money ledger; BillingCore's lock serialises writes in-process; threads are safe, multiple writer processes are not - stated in the Dockerfile and OPS.md). - docker-compose.yml: named volume saas_data as the durability story; SAAS_API_KEY defaults to empty -> fail-closed 503, /health stays public. - OPS.md: start/stop, online backup (sqlite3 backup API), restore procedure (incl. clearing WAL sidecars), key rotation, down-checklist, scaling notes. - .dockerignore: runtime files only. Test evidence (local on branch issue-40; no VPS deploy under this ticket): - python -m pytest -q: 273 passed, 1 skipped. - docker compose up --build: /health 200, container health=healthy. - Persistence: client created + credited 2500 -> down/up -> read back intact. - Restore drill: backup -> drift client B -> restore -> integrity_check ok, client A (balance 2500) present, client B absent (404). Closes #40 once merged + deployed; the VPS run is a separate owner-approved step.
42 lines
1.5 KiB
YAML
42 lines
1.5 KiB
YAML
# gitea-saas-core deployment (issue #40).
|
|
#
|
|
# The MEANING OF THE VOLUME (read before changing it):
|
|
# The service stores its money ledger in SQLite at /data/saas.db. The
|
|
# named volume `saas_data` is therefore the ENTIRE durability story:
|
|
# losing the volume loses the ledger. `docker compose down` keeps the
|
|
# volume. `docker compose down -v` DESTROYS it — never run that without
|
|
# a verified backup (OPS.md "Backup"/"Restore"). Backups before traffic.
|
|
#
|
|
# SQLite is a single-writer store: the container runs exactly one gunicorn
|
|
# worker (see Dockerfile) so all writes funnel through one process lock.
|
|
# Do not raise --workers here.
|
|
name: gitea-saas
|
|
|
|
services:
|
|
saas:
|
|
build: .
|
|
image: gitea-saas-core:local
|
|
restart: unless-stopped
|
|
ports:
|
|
- "8080:8080"
|
|
environment:
|
|
# Empty by default on purpose: with no key the app still boots and
|
|
# /health stays public, but every authed route fails closed with 503
|
|
# (see gitea_saas/app.py). Set a real key before serving traffic:
|
|
# docker compose --env-file .env up -d
|
|
# with SAAS_API_KEY=<secret> in .env, or export SAAS_API_KEY first.
|
|
SAAS_API_KEY: ${SAAS_API_KEY:-}
|
|
SAAS_DB_PATH: ${SAAS_DB_PATH:-/data/saas.db}
|
|
SAAS_PORT: "8080"
|
|
volumes:
|
|
- saas_data:/data
|
|
healthcheck:
|
|
test: ["CMD", "python", "-c", "import sys,urllib.request; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=5).status == 200 else 1)"]
|
|
interval: 30s
|
|
timeout: 5s
|
|
start_period: 10s
|
|
retries: 3
|
|
|
|
volumes:
|
|
saas_data:
|