Adding a tenant¶
Standing up one client on an existing host. For a new host, see host setup.
Pending the role
The tenant_stack role is a stub — it depends on the core stack, which depends on the brain and registry images that do not exist yet. This page records the decided sequence so it is not reinvented later; the playbook command at step 6 does not work today.
The sequence¶
flowchart TB
A["1 · Agree the slug<br/><b>permanent</b>"] --> B["2 · Create the tenant<br/>record in the registry"]
B --> C["3 · Vault password<br/>+ into Bitwarden"]
C --> D["4 · tenants/{slug}/vars.yml<br/>+ vault.yml"]
D --> E["5 · Inventory entry"]
E --> F{"6 · provision-tenant.yml<br/><b>registry pre-flight gate</b>"}
F -->|"no matching record,<br/>or region mismatch"| G["FAILS CLOSED<br/>fix the registry, retry"]
G --> B
F -->|"match"| H["Stack up:<br/>Postgres · n8n · app<br/>· Metabase if bought"]
H --> I["7 · DNS record<br/>created automatically"]
I --> J["8 · Verify + reconcile"]
Step by step¶
1. Agree the slug — it is permanent¶
The slug appears in DNS, TLS certificates, database names, vault IDs, directory paths and backup paths. It does not change if the client rebrands (ADR-0022 §7).
Lowercase, alphanumeric, no hyphens where avoidable. greenhills, not Greenhills Electrical Pty Ltd.
2. Create the tenant record in the registry¶
Via the BOS Console. Must include region — the pre-flight gate at step 6 checks it against inventory and fails closed if they disagree (ADR-0024 §6).
This prevents a tenant existing in infrastructure but not in the map: the state where nothing knows who owns the data.
3. Vault password, then Bitwarden¶
Each tenant gets its own --vault-id, so one leaked password does not open every client (ADR-0027 §3).
Do this before creating any secrets
Provisioning is not complete until the new vault password is in the shared Bitwarden collection (ADR-0024 §11). A vault password only one partner holds is a bus-factor failure that stays invisible until it matters.
4. Tenant variables¶
infra/tenants/{slug}/vars.yml # non-secret: region, services, limits
infra/tenants/{slug}/vault.yml # secrets, --vault-id {slug}
services decides what gets deployed:
| Value | Deploys |
|---|---|
[core, n8n] |
Postgres, n8n, n8n's Postgres, review app |
[core, n8n, metabase] |
The above plus ~2 GB of Metabase |
Only include metabase for clients who bought the dashboard product (ADR-0023 §5). Check the sizing worksheet before adding one to a host near capacity.
Secrets use inline encrypt_string, so variable names stay readable and the change is reviewable in a PR without decrypting.
5. Inventory entry¶
A tenant is an inventory host whose ansible_host points at the physical box (ADR-0024 §1):
tenants:
hosts:
greenhills:
ansible_host: <bos1 ip>
tenant_slug: greenhills
region: au-mel
services: [core, n8n, metabase]
This is what makes a later move to dedicated hardware a one-line change.
6. Provision ⏳¶
cd infra
ansible-playbook playbooks/provision-tenant.yml \
--limit greenhills --vault-id greenhills@prompt
If the pre-flight gate fails, fix the registry record — do not reach for --force. That flag exists for genuine recovery and requires a logged reason.
7. DNS¶
Created automatically by the dns role using the zone-scoped Cloudflare token. The wildcard *.app.octopodia.com.au already covers the hostname, so nothing is created by hand.
Deletion is never automated
Deprovisioning marks records for removal; a human executes it. A wrong DNS deletion presents to a client as total outage and persists in resolver caches after the mistake is fixed (ADR-0022 §8).
8. Verify¶
| Check | Expected |
|---|---|
https://{slug}.app.octopodia.com.au |
Loads, valid certificate |
/insights |
Metabase, only if in services |
| Registry reconciliation | Tenant present, region matches |
| Containers | On the tenant's private network, not reachable from another tenant's |
| Backup | Appears in that tenant's path after the first nightly run |
| Vault password | In Bitwarden, both partners |
Before the first paying client¶
Client-zero — Octopodia's own inbox — runs on the shared host for roughly a month first. We do not learn tenant provisioning on a client who is paying.
Greenhills is the first client tenant, not tenant one.