gitea-saas-core#40: deploy — container + ops doc, running on the VPS #50
No reviewers
Labels
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: pbwraith/gitea-saas-core#50
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "issue-40"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #40 (deploy: container + ops doc).
Scope delivered (the four files this issue owns)
Dockerfile– python:3.12-slim, non-rootappuser,gunicorn wsgi:application, HEALTHCHECK on the public/healthroute (urllib, no curl in slim).docker-compose.yml– service + named volumesaas_data. The volume IS the durability story (SQLite ledger): losing it loses the ledger;down -vis called out as destructive in the comments and OPS.md. Composename:pinned togitea-saasso the volume/project names are deterministic for the documented restore.OPS.md– start/stop, online backup (sqlite3 backup API, not a livecp), restore procedure (with mandatory WAL-sidecar cleanup), key rotation, down-checklist, scaling limits..dockerignore– runtime files only.Ledger-safety decision (issue requirement: state which you chose and why)
Single gunicorn worker (
--workers 1), threads for concurrency. The store is SQLite (WAL on,busy_timeout5s, writes serialised by BillingCore's single in-process lock). Multiple worker processes would be multiple independent writers racing on one ledger file; WAL permits one writer at a time but a money ledger must not depend onSQLITE_BUSYbackoff at the file level. One worker keeps every write behind the one lock. Scaling beyond that requires a network DB (documented in OPS.md "Scaling"), deliberately out of scope.Acceptance evidence (all run for real on this WSL box, branch
issue-40)docker compose up --buildon the branch with no host Python involved:/health->{"ok":true}HTTP 200;docker inspecthealth = healthy.docker compose down && up, client A read back with balance 2500 intact (volume-mounted WAL-mode SQLite).docker compose exec ... sqlite3 backup API-> archived to host; created drift client B;docker compose down;docker run --rm -v gitea-saas_saas_data:/data ... rm -f saas.db-wal saas.db-shm && cp backup;up -d. Result recorded:PRAGMA integrity_check-> ok; clients -> persist-test-A only; ledger rows -> 1; API: client A (balance 2500) 200, client B 404.python -m pytest -qon the branch: 273 passed, 1 skipped.git diff --checkclean.Execution boundary (Vale) — what was NOT done
No VPS deploy, no public exposure, no paid provisioning, no production credential changes, no live ledger writes: validated locally on branch
issue-40, one PR, per the owner decision comment. The "running on the VPS" outcome is deliberately left as the separate owner-approved deployment step; OPS.md is the runbook for that step.Reviewed by Claude Opus (reviewer/merge gate).
Gate: PASS — pytest, run against the PR head in an isolated worktree.
Purely additive: Dockerfile, docker-compose.yml, .dockerignore, OPS.md. No changes to application source, so the existing suite is a sufficient gate.