Deploy: container + ops doc, running on the VPS #40

Closed
opened 2026-09-01 10:36:51 +02:00 by pbwraith · 2 comments
Owner

Outcome

The service runs somewhere other than a developer's shell. Verified on main:
there is no Dockerfile, docker-compose, Procfile, systemd unit, .gitea/workflows
or ops document. A billing service that is not hosted cannot take money.

Dependency

Dependency satisfied: app assembly #38 merged via PR #43. Verified current main contains gitea_saas/app.py:create_app and root wsgi.py.

Scope

  • Dockerfile -- slim Python base, non-root user, gunicorn wsgi:application.
  • docker-compose.yml -- the service plus a persistent volume for the SQLite
    file. SQLite is the current store, so the volume is the durability story:
    losing it loses the ledger. Say that in the compose comments.
  • OPS.md -- how to start, stop, back up the DB, rotate SAAS_API_KEY, and
    what to check when it is down.
  • Health: container HEALTHCHECK hitting /health.

Ledger safety -- read before choosing anything clever

This process owns a money ledger. Two constraints that are not negotiable:

  1. Single writer. SQLite with multiple gunicorn workers writing the ledger
    will corrupt or lock. Either run one worker, or enable WAL and serialise
    writes explicitly. State which you chose and why in the PR.
  2. Backups before traffic. A documented, tested restore -- not just a
    cp in a comment. A ledger with no restore path is worse than no ledger,
    because it looks reliable.

Files this issue owns

Dockerfile, docker-compose.yml, OPS.md, .dockerignore.

Acceptance

  1. docker compose up serves /health on a clean checkout with no host Python.
  2. The DB survives docker compose down && up -- asserted by creating a client,
    restarting, and reading it back.
  3. OPS.md's restore procedure is followed end-to-end once and the result recorded
    in the PR. Do not mark this done on an untested restore.

Execution boundary (Vale)

Prepare and validate locally first, on branch issue-40; no production credential changes, public exposure, paid provisioning, or live ledger writes. If Docker/VPS access is unavailable, report the exact blocker and leave deployment acceptance incomplete. Never substitute mocks for required Docker persistence/restore tests. All other files forbidden. Run python -m pytest -q plus the container/persistence/restore acceptance commands and record real outputs.

## Outcome The service runs somewhere other than a developer's shell. Verified on `main`: there is no Dockerfile, docker-compose, Procfile, systemd unit, `.gitea/workflows` or ops document. A billing service that is not hosted cannot take money. ## Dependency **Dependency satisfied:** app assembly #38 merged via PR #43. Verified current main contains `gitea_saas/app.py:create_app` and root `wsgi.py`. ## Scope - `Dockerfile` -- slim Python base, non-root user, `gunicorn wsgi:application`. - `docker-compose.yml` -- the service plus a persistent volume for the SQLite file. SQLite is the current store, so **the volume is the durability story**: losing it loses the ledger. Say that in the compose comments. - `OPS.md` -- how to start, stop, back up the DB, rotate `SAAS_API_KEY`, and what to check when it is down. - Health: container `HEALTHCHECK` hitting `/health`. ## Ledger safety -- read before choosing anything clever This process owns a **money ledger**. Two constraints that are not negotiable: 1. **Single writer.** SQLite with multiple gunicorn workers writing the ledger will corrupt or lock. Either run one worker, or enable WAL and serialise writes explicitly. State which you chose and why in the PR. 2. **Backups before traffic.** A documented, tested restore -- not just a `cp` in a comment. A ledger with no restore path is worse than no ledger, because it looks reliable. ## Files this issue owns `Dockerfile`, `docker-compose.yml`, `OPS.md`, `.dockerignore`. ## Acceptance 1. `docker compose up` serves `/health` on a clean checkout with no host Python. 2. The DB survives `docker compose down && up` -- asserted by creating a client, restarting, and reading it back. 3. OPS.md's restore procedure is followed end-to-end once and the result recorded in the PR. Do not mark this done on an untested restore. ## Execution boundary (Vale) Prepare and validate locally first, on branch `issue-40`; no production credential changes, public exposure, paid provisioning, or live ledger writes. If Docker/VPS access is unavailable, report the exact blocker and leave deployment acceptance incomplete. Never substitute mocks for required Docker persistence/restore tests. All other files forbidden. Run `python -m pytest -q` plus the container/persistence/restore acceptance commands and record real outputs.
pbwraith added the
blocked
p1
labels 2026-09-01 10:36:52 +02:00
Author
Owner

[owner-decision] Euro requested execution of the income methods. Dependency verified on current main: PR #43 is merged and app.py/create_app plus wsgi.py exist. Removing stale dependency block; implement the scoped ticket, test, and open one PR. No public launch, real payments, or production ledger changes under this ticket without separate approval.

[owner-decision] Euro requested execution of the income methods. Dependency verified on current main: PR #43 is merged and app.py/create_app plus wsgi.py exist. Removing stale dependency block; implement the scoped ticket, test, and open one PR. No public launch, real payments, or production ledger changes under this ticket without separate approval.
pbwraith removed the
blocked
label 2026-09-05 11:08:41 +02:00
pbwraith added the
in-progress
label 2026-09-05 11:12:46 +02:00
pbwraith added
in-review
and removed
in-progress
labels 2026-09-05 11:19:11 +02:00
Author
Owner

[lane1] implemented #40 on branch issue-40, commit 5be23b9; PR #50 open for review.

Host gate: python -m pytest -q -> 273 passed, 1 skipped; git diff --check clean.

Container acceptance (all executed locally, branch issue-40, no host Python for the service):

  • docker compose up --build -> /health HTTP 200 {"ok":true}; docker inspect health=healthy.
  • Persistence: client A + credit 2500 -> docker compose down && up -> balance 2500 read back intact (named-volume SQLite).
  • Restore drill (OPS.md procedure run end-to-end once): online backup via sqlite3 backup API; created drift client B; down; WAL sidecars removed + backup copied into gitea-saas_saas_data; up. Verified: PRAGMA integrity_check -> ok, clients=[persist-test-A], ledger rows=1, client A 200 / client B 404.
  • Ledger-safety choice: single gunicorn worker + threads (one writer process behind BillingCore's lock); scaling beyond SQLite documented in OPS.md.

Deployment boundary respected: local validation only, no VPS run / public exposure / live ledger writes (owner-approved scoped ticket). The VPS deployment step itself stays a separate owner action.

[lane1] implemented #40 on branch `issue-40`, commit `5be23b9`; PR #50 open for review. Host gate: `python -m pytest -q` -> **273 passed, 1 skipped**; `git diff --check` clean. Container acceptance (all executed locally, branch `issue-40`, no host Python for the service): - `docker compose up --build` -> `/health` HTTP 200 `{"ok":true}`; `docker inspect` health=healthy. - Persistence: client A + credit 2500 -> `docker compose down && up` -> balance 2500 read back intact (named-volume SQLite). - Restore drill (OPS.md procedure run end-to-end once): online backup via sqlite3 backup API; created drift client B; down; WAL sidecars removed + backup copied into `gitea-saas_saas_data`; up. Verified: `PRAGMA integrity_check` -> ok, clients=[persist-test-A], ledger rows=1, client A 200 / client B 404. - Ledger-safety choice: single gunicorn worker + threads (one writer process behind BillingCore's lock); scaling beyond SQLite documented in OPS.md. Deployment boundary respected: local validation only, no VPS run / public exposure / live ledger writes (owner-approved scoped ticket). The VPS deployment step itself stays a separate owner action.
Sign in to join this conversation.
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: pbwraith/gitea-saas-core#40
No description provided.