Infrastructure
Place each process only after the deployment checklist is confirmed against the ScalaHosting plan. Until then, the production URL and the machine layout stay undecided. The logical separation does not.
Processes
| Process | Public | State |
|---|---|---|
| Next.js | Yes, HTTPS | No payment secrets and no database role. |
| Express | Webhooks only, plus private calls from Next.js | Business data and provider secrets. |
| Worker | No | Jobs, mail, thumbnails, refund calls. |
| PostgreSQL | No | System of record. TLS from the API and worker. |
| Object storage | No public objects | Originals and thumbnails. |
| Docusaurus | Yes, static HTTPS | Documentation only. |
Database
- PostgreSQL 15 or newer.
- The API, the worker, and Prisma migrations connect with
DATABASE_URLonly. There is no second connection string. - The database user in that URL is not a superuser. Append-only tables reject update and delete in the database.
- Daily logical backup plus the host's volume snapshots if they exist. See recovery.
Storage
The bucket policy denies public reads. The API signs GetObject for 300 seconds after the gallery check. The worker's credential can write thumbnails and read originals. It is not embedded in Next.js.
Email and payments
The Stripe account is USD. The webhook endpoint is the public Express route, with the signing secret from the environment. Mail is sent by the worker. A failed delivery stays on notification_deliveries and is retried.
Monitoring
Track at least:
- Webhook signature failures and unique-constraint conflicts on
payment_webhook_events. - Orders remaining in
PENDING_PAYMENTafter a provider payment exists. - Jobs in
FAILED, and the age of the oldest pending job. - Download responses refused because access is not approved. A spike is a signal to inspect, not by itself a defect.
- Backup success and the time of the last restore drill.
Logs omit passwords, access tokens, refresh tokens, setup tokens, card data, and storage secrets. Audit metadata follows the same rule.
Documentation host
Publish kstudeo-doc as a static site. Set url in the Docusaurus config to that host before the production build so canonical links are correct.