Multi-Tenancy Model
How a tenant is put together, how requests are routed to one, and the procedures for adding, deleting, and rebuilding the template new tenants clone from. For which tenants actually exist on staging today (URLs, databases, buckets, users), see Environments → Tenants on staging.
How a tenant is put together
Every tenant is one row in tm.tenant in the mgmt database, which points at:
- its own Postgres database — cloned from the
tenant_basegolden template, on the same Railway Postgres server asmgmtandfinance - two RustFS/S3 buckets —
dbucket(default) andtbucket(temp, 1-day expiry), named as unguessable UUIDs generated server-side so no tenant can guess another's - a domain —
domainnameon the tenant row, matched on the full FQDN
Requests are routed to the right tenant by host. On staging each tenant needs its own DNS record (single-label <tenant>-app.finaisse.com); there is no wildcard, which is why a tenant can be enabled and still unreachable if the record is missed.
Tenant routing only works when TENANCY_ENABLED=true on blitz-api (the service that resolves tenant from host). It is currently true on blitz-api, ws, wfw and blitz-apimgmt.
Adding a tenant
Creation is done from the management portal and is documented in blitz/src/docs/tenant-db-provisioning.md — deliberately not duplicated here. After creating one, add it to Environments → Tenants on staging and a DNS record.
Deleting a tenant leaves its data behind
Removing a tenant row does not drop its database or its RustFS buckets — that is deliberate (compensation is manual), but it means deleted tenants leave orphans.
Worse, the bucket names are lost with the row. dbucket/tbucket are UUIDs stored only on the tenant row, mgmt keeps no audit history, and nothing on the RustFS side ties a bucket back to a tenant. Once the row is gone the orphaned buckets are unidentifiable except by elimination against the remaining tenants.
So capture dbucket and tbucket before deleting a tenant.
Audit orphans by comparing pg_database against tm.tenant. A demo2 tenant deleted on 2026-08-24 left a ~57 MB database behind — dropped 2026-08-24 after a verified backup. Its buckets could not be identified afterwards and remain orphaned on RustFS, which is exactly the failure this warning is about.
Rebuilding the tenant_base template
New tenants are clones of tenant_base, so it must track finance. Rebuild steps are in blitz/src/docs/tenant-db-provisioning.md; two things routinely go wrong:
- A rebuild must end by re-locking the template. A freshly created database allows connections, and
CREATE DATABASE … TEMPLATE tenant_basefails with "source database is being accessed by other users" if anything connects. Finish with:sqlVerify withALTER DATABASE tenant_base WITH ALLOW_CONNECTIONS false;select datallowconn from pg_database where datname='tenant_base'— it must befalse. (Confirmedfalseon staging as of 2026-08-24.) - Do not check staleness by file mtime — the obvious
PG_VERSIONcheck reports the directory's age, not the schema's. Compareinformation_schema.columnsbetween template and source instead (see Environments → Tenants on staging for thedefaulttenant's verified comparison).
Related
- Environments → Tenants on staging — which tenants exist, their URLs, databases, buckets, users
- Staging DB Restore Console — restoring a tenant DB