0004 — Tenant config: repo bootstraps, DB authoritative at runtime
- Status: accepted
- Date: 2026-05-06
- Deciders: Derek
Context
Each tenant has settings: org name, domains, theme name, enabled modules, integration credentials, content type seed, branding assets. Two failure modes to avoid:
- DB-only config. Nothing in git, no diff for proposals, no review trail, can’t bootstrap a new tenant from a config file. Any change to a tenant’s config is invisible to engineers.
- Repo-only config. Admins can’t change anything without a deploy. Kills the “you own this” promise and forces engineering to be a bottleneck on every tenant change.
Decision
A two-layer config model:
tenants/<slug>/tenant.config.ts— bootstrap source of truth. Validated via Zod schema inpackages/tenant-config. Used to create the org row at onboarding time. Lives in git (or a private per-tenant repo).organizationsrow in the DB — runtime source of truth. Mutable from the admin UI. Drives the live system once the tenant exists.
A nightly job compares the two and surfaces drift. Drift isn’t an error — admins are supposed to change runtime values from the UI. Drift is informational: “this tenant’s runtime diverges from its bootstrap config; if you re-bootstrap from tenant.config.ts, here’s what would change.”
Consequences
Easier:
- A new tenant is
cp -R tenants/_example tenants/new-org && edit && pnpm new-tenant new-org— one command end-to-end - Engineers get diff/review on tenant changes that ride in PRs
- Admins get UI-driven changes for everything else — no “wait for a deploy”
- Disaster recovery: re-bootstrap a tenant from its config file; restored data is immutable
Harder:
- Drift is a real concept; we explain it in the admin UI and provide tools to reconcile
- Two writers, one truth — the contract is that
tenant.config.tsis authoritative at bootstrap time only; after that, the DB wins until explicitly re-bootstrapped
Alternatives considered
- DB-only with admin UI. Loses git review for engineering changes. Bootstrap becomes “click through admin UI” — bad for AI iteration and bad for repeatability.
- Repo-only. Admins file PRs to change their own org’s name. Hard veto.
- Single source via “config is data, data is config” (Hasura-style). Conceptually pure but the operational complexity isn’t worth it at this scale.