A Next.js shell owns identity, per-app entitlements and access codes. Caddy forward_auth gates each tool, and each tool runs as its own process or container, in any language. I cut it over from serverless to one hardened VPS on 2026-08-20.
Every tool I shipped needed auth, access control and hosting. The aim was one gated front door that makes the next tool cheap to add, for 10-20 invited users and with no public signup.
browser
|
v
Caddy 2 --- forward_auth ---> Next.js 16 shell
| <-- 200 + X-Auth-* -- identity, entitlements,
| billing-by-code, admin
| |
| PostgreSQL 16 (Drizzle)
| closed to the internet on 3 layers
v
tool in-shell | own process | own container (Go / Python / Node)
X-Auth-User: u_123 X-Auth-Apps: tool-a,tool-b
platform-lite fork: no auth code, fails closed if the header is missing
Docker · systemd · nightly backups with a verified restore
| date | result |
|---|---|
| 2026-08-18 | Initial commit. First in-shell tool and the trading tool live |
| 2026-08-19 | A tool served 502s to every logged-in user for 12 hours. Full audit; the architecture doc was rewritten because the documented setup had never run; live config committed; nightly backups added and a restore verified |
| 2026-08-20 | Cutover: one shell on the VPS behind Caddy, the serverless host retired, Postgres closed to the internet on three layers, the ufw/Docker gap closed |
| 2026-09-12 | Landing page recovered from files that existed in no repo, committed as a baseline, reworked and deployed. The trading WebSocket 502 open since 2026-08-19 was fixed in the v0.3.0 working tree |
| user | tool-a | tool-b | tool-c | admin |
|---|---|---|---|---|
| [User 1] | [x] | [x] | [x] | [x] |
| [User 2] | [x] | [ ] | [x] | [ ] |
| [User 3] | [ ] | [x] | [ ] | [ ] |
| [User 4] | [x] | [ ] | [ ] | [ ] |