Deployment¶
Reference stack¶
The reference production deployment runs on a single Linux host (bosun — HPE ML350 Gen10) using Docker Compose with Caddy as the reverse proxy. Services are separated by compose project:
/home/sauer/bosun/compose/
├── saebooks-sauer/ # saebooks-api + saebooks-web + postgres
│ ├── docker-compose.yml
│ └── .env # secrets, not committed
├── discourse/ # discourse.saebooks.com.au
├── edge-proxy/ # Caddy reverse proxy + ACME
└── ...
The Postgres data volume is a named volume (db_data) within the
compose project. Mail outbox files land at /opt/data/saebooks-sauer/mail-outbox
on the host.
Caddy routing¶
The web container is reachable from Caddy over the shared revproxy
Docker network (compose service name web, internal port 8080).
The API container (api, internal port 8000) is never exposed
publicly — the web process reaches it via the internal compose network
at http://api:8000. A gRPC-Web proxy (grpcwebproxy) is also
present for the desktop client.
Environment variables¶
saebooks-api¶
| Variable | Required | Notes |
|---|---|---|
DATABASE_URL |
yes | Owner-role (BYPASSRLS) URL — used by Alembic migrations and pre-auth lookups (LoginSessionLocal). postgresql+asyncpg://saebooks:…@db:5432/<dbname> |
SAEBOOKS_APP_DATABASE_URL |
yes (prod) | Runtime URL for the non-superuser saebooks_app role — required when SAEBOOKS_ENV is not dev/test/ci; see migration 0056_split_db_role. |
SAEBOOKS_APP_DB_PASSWORD |
yes (prod) | Password for saebooks_app; also read by migration 0128_app_role. |
SAEBOOKS_SECRET_KEY |
yes | HMAC secret for issued JWTs; 32+ random bytes. If unset, a per-process random key is generated (safe for dev/single-process only). |
SAEBOOKS_EDITION |
no | community (default), offline, business, pro, enterprise. When set to any value other than community, the licence resolver is bypassed entirely (USB and portal JWT drivers are skipped). Intended for development; a warning is logged on startup. |
STRIPE_SECRET_KEY |
for billing | sk_test_… or sk_live_… |
STRIPE_WEBHOOK_SECRET |
for billing | whsec_… |
saebooks-web¶
| Variable | Required | Notes |
|---|---|---|
SAEBOOKS_WEB_API_URL |
yes | Base URL of saebooks-api (e.g. http://api:8000) |
SAEBOOKS_WEB_SECRET_KEY |
yes | Session cookie signing secret |
DISCOURSE_SSO_SECRET |
for DiscourseConnect | Shared secret for DiscourseConnect /auth/discourse/* |
SAEBOOKS_WEB_SITE_ORIGIN |
yes | Comma-separated allowed origins for CSRF enforcement |
SAE_STAFF_USERNAMES |
no | Comma-separated staff usernames for admin bypass |
Build and deploy¶
The compose files use local build contexts (no registry). To deploy a code change:
# On bosun
cd /home/sauer/bosun/compose/saebooks-sauer
sudo docker compose build web
sudo docker compose up -d web
To update the API (includes Alembic upgrade):
sudo docker compose build api
sudo docker compose up -d api
# Alembic upgrade head runs on container start via entrypoint.sh
Health probes¶
| Path | Service | Purpose |
|---|---|---|
/healthz |
api, web | Liveness — always 200 while the process is up (no DB round-trip) |
/api/v1/healthz |
api | Canonical liveness path for external probes |
/api/v1/version |
api | Edition + package version (unauthenticated) |
/api/v1/docs |
api | Swagger UI (restrict in prod) |
Note
A /readyz readiness probe has not yet been implemented. The
/healthz liveness probe intentionally skips the DB round-trip.
Log access¶
sudo docker logs saebooks-sauer-web-1 --since 30m
sudo docker logs saebooks-sauer-api-1 --since 30m -f
The web process tags logs saebooks_web; the API tags saebooks.
Backup¶
Nightly pg_dump of the database via a cron job or make backup
(which runs scripts/backup.sh). The Postgres data volume is the
named volume db_data within the compose project.