Per-tenant config (multi-org in one deployment)
Serve multiple customer organizations from one Olympus deployment
Olympus is designed dual-domain (CIAM/IAM), not N-tenant. If you need separate identity domains for many customer organizations, you have three options.
Option 1: One Olympus deployment per tenant
The Olympus way. Each tenant gets:
- Their own VPS.
- Their own Olympus stack.
- Complete isolation.
Pros: Clean isolation. Each tenant gets bespoke configuration. Compliance is straightforward.
Cons: Operational overhead × N. Cost × N.
Suitable for: enterprise B2B with <20 customers, each willing to pay.
Option 2: Tenant as a Kratos schema discriminator
One Olympus deployment, all tenants share Kratos. Identity schema includes a tenant_id trait:
"tenant_id": {
"type": "string",
"pattern": "^[a-z0-9-]+$",
"title": "Organization"
}Each registration assigns a tenant_id. Your apps filter by tenant_id on every query.
Pros: Cost-effective for many small tenants.
Cons: Single point of failure for all tenants. A bug in your filter logic leaks data across tenants. Not really isolated.
Suitable for: many small tenants in a B2C-ish product where data sharing risk is low.
Option 3: Tenant-per-CIAM-via-domain
Multiple subdomains, all proxied to the same Olympus deployment, but Hera renders per-tenant branding/config based on hostname.
acme.your-saas.com → ciam-hera (with acme branding)
foo.your-saas.com → ciam-hera (with foo branding)Implementation: Hera reads Host header, looks up the tenant config from your settings vault.
const tenantId = req.headers.get("host")?.split(".")[0];
const tenantConfig = await getSetting("ciam", `tenant.${tenantId}.config`);
// Use tenantConfig.logo, tenantConfig.allowed_emails, etc.Still one Olympus deployment, but per-tenant cosmetics.
What stays single-tenant in any option
- The Olympus deployment itself.
- Kratos and Hydra binaries.
- Postgres (different databases per tenant if Option 1; shared otherwise).
- Caddy.
Mixing options
Common pattern:
- Option 1 for enterprise tier (compliance-sensitive customers).
- Option 3 (per-domain branding) for self-service tier.
- Option 2 rare, only when truly N independent identities matter.
Per-tenant OAuth2 clients
Even in Option 1, each tenant's OAuth2 clients are still managed via Athena → OAuth2 Clients. No "tenant" concept inside Hydra, just clients.
If you need per-tenant scope namespacing: prefix scopes with tenant ID (acme:read:widgets etc.).
Per-tenant settings
Use the settings vault with tenant-prefixed keys:
await getSetting("ciam", `tenant.${tenantId}.smtp.host`);
await getSetting("ciam", `tenant.${tenantId}.brand.logo_url`);Wire your apps to read tenant-scoped settings.
Caveats
- Cookie domain, if multiple tenant subdomains share
your-saas.comas cookie domain, sessions accidentally span tenants. - OAuth2 clients with redirect URIs to wrong tenants, verify on registration.
- Email "from" address, should match the tenant, not your generic SaaS.