Standing up a self-hosted Notion alternative on a busy production node without touching its existing shared database, and catching three non-obvious failure modes along the way.
The challenge
The production node already ran a shared database and a number of live apps. I needed to add a self-hosted Notion alternative, a multi-container stack (its own database, cache, and object store, plus web, gateway, and auth services), onto that same node without disturbing any of the existing data or services. The stack also had to fit the house deploy conventions: pinned image versions, excluded from auto-updates, secrets generated on the box, loopback-only binding fronted by an HTTPS reverse proxy, and no app calling an AI provider directly.
Approach
I ran the deploy through a numbered, gated checklist (privileged steps paused for explicit approval) and made isolation the load-bearing decision.
- Isolation-first datastores. The stack bundles its own database, cache, and object store. Rather than point it at the node's existing shared database, I kept the entire datastore layer on the stack's own isolated network and deliberately skipped any shared-schema step. A bug in the new app simply cannot reach production data.
- Two-port loopback split. I published the internal gateway on loopback only and let the private VPN's HTTPS terminator own the external port and forward to it. Binding the gateway to all interfaces would have let Docker's listener pre-empt the terminator and throw a 502: a known trap.
- Defer the non-compliant subsystem. The stack's AI container called a cloud provider directly, which violates the rule that all AI traffic route through an AI gateway. I dropped the AI and search containers at deploy time and filed a follow-up to re-add them additively through the gateway. Ship a clean, compliant core first.
Results
- Live in production, VPN-only, every container on an isolated network, every image version-pinned and excluded from auto-updates: the node's first internal object store.
- Verified healthy at every layer: container health checks, a loopback probe, and end-to-end through the VPN's HTTPS terminator with valid TLS and no 502, confirming the two-port split was correct.
- No data risk materialized and isolation held throughout, despite two mid-deploy snags, because the datastores were bundled and isolated by design.
- Honestly scoped: verification was functional-smoke-level (health checks plus a signup and page-persistence test), not load or backup-restore testing. AI/search re-enablement and a backup/restore procedure for the bundled datastores were recorded as open items, not claimed done.
Three non-obvious catches
- Split image-version line. The web image was pinned on a separate line from the cloud images, so the first pull failed. The authoritative version source was the container registry, not the releases API that reported a stale number. Pin each image separately, verify at the authoritative registry.
- Mail-less login dead-end. The default login is a passwordless magic link that needs SMTP to deliver. With no mail server, signup created the user but the link never arrived, producing a confusing "already registered" loop. The password signup form works fully without mail (auto-confirm), so that's the working path on a mail-less node.
- Self-recreating admin account. The auth service recreates its admin user from a configured email on every startup, so that address stayed permanently "taken," even across database wipes. I pointed the admin config at a throwaway address, signed up with the real email, then promoted that account to admin directly.
What this demonstrates
- Blast-radius thinking as a default: bundling and isolating a third-party stack's datastores protects existing production data far better than sharing them.
- Disciplined, gated execution: a numbered checklist with human approval on privileged steps, live pre-flight checks instead of assumptions, and on-box secret generation.
- Real troubleshooting depth: three failure modes that container health alone would never surface: caught by testing the actual flows, not just green checkmarks.
- Compliance without shortcuts: deferring a non-compliant subsystem rather than hacking it in kept the deploy clean and reversible.