Skill — Multi-Tenancy Patterns
When this skill activates
Any task involving multi-tenant architecture, tenant isolation, shared database
strategies, tenant context propagation, or tenant provisioning workflows.
Mandatory actions when this skill is active
Before designing multi-tenancy
- Assess isolation requirements (regulatory, security, performance).
- Determine tenant scale (10 tenants vs 10,000 tenants).
- Choose isolation level based on business requirements, not engineering convenience.
Isolation levels
Shared Database + Row-Level Security (RLS):
- Single database, single schema, tenant_id column on every table.
- Cheapest to operate, easiest to deploy.
- Least isolated — bugs can leak data between tenants.
- Best for: SaaS with many small tenants, cost-sensitive.
- Risk: a missing WHERE clause exposes all tenant data.
Schema per tenant:
- Single database, separate schema per tenant.
- Moderate isolation and moderate cost.
- Migrations must run per-schema (automation required).
- Best for: moderate tenant count (< 1000), need logical separation.
Database per tenant:
- Completely separate database per tenant.
- Maximum isolation, maximum cost.
- Independent scaling, independent backup/restore.
- Best for: enterprise customers, regulatory requirements, high-value tenants.
RLS implementation (PostgreSQL)
-- Enable RLS on the table
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
-- Create policy that filters by tenant
CREATE POLICY tenant_isolation ON orders
USING (tenant_id = current_setting('app.current_tenant')::uuid);
-- Force RLS even for table owner
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
-- Set tenant context at connection level
SET app.current_tenant = 'tenant-uuid-here';
Critical rules:
- RLS policies must be impossible to bypass from application code.
- NEVER use a superuser connection that bypasses RLS in application code.
- Test RLS with a dedicated "tenant leak" test suite.
- Add a CHECK constraint:
tenant_id IS NOT NULL on every tenant-scoped table.
Tenant context propagation
Middleware pattern:
Request → Extract tenant → Validate → Set context → Handler → Response
Extraction sources (priority order):
- JWT claim (most secure — server-signed).
- Subdomain:
tenant1.app.com → tenant_id lookup.
- Path prefix:
app.com/tenant1/... → extract from URL.
- Header:
X-Tenant-ID (for service-to-service only).
Propagation through the stack:
- HTTP layer: middleware sets tenant in request context.
- Service layer: receives tenant from context, passes to repositories.
- Data layer: sets session variable before executing queries.
- Background jobs: serialize tenant_id in job payload, restore before processing.
Tenant routing
Subdomain routing:
tenant1.app.com, tenant2.app.com
- Requires wildcard DNS and TLS certificate.
- Clean separation, easy to identify tenant.
- Custom domains: CNAME tenant1.com → tenant1.app.com.
Path-based routing:
app.com/tenant1/dashboard, app.com/tenant2/dashboard
- Simpler DNS/TLS setup.
- Requires path prefix stripping in routing layer.
Header-based routing (internal only):
X-Tenant-ID: tenant-uuid
- For service-to-service communication within the platform.
- Never expose to end users (spoofing risk).
Tenant provisioning
Onboarding workflow:
- Create tenant record (name, plan, config).
- Provision resources (schema/database if applicable).
- Run seed data (default roles, settings, sample data).
- Configure DNS/routing (subdomain or custom domain).
- Send welcome notification.
Automation requirements:
- Provisioning must be fully automated (no manual steps).
- Must complete in < 30 seconds for shared DB, < 5 minutes for isolated DB.
- Rollback on failure — no half-provisioned tenants.
Tenant-aware migrations
Shared DB (RLS):
- Standard migrations — all tenants share the schema.
- Add tenant_id to new tables, backfill existing tables.
Schema per tenant:
- Migration runner must iterate all tenant schemas.
- Parallel execution for speed, with error handling per-tenant.
- Failed migrations must not block other tenants.
Database per tenant:
- Migration runner connects to each tenant DB.
- Version tracking per-tenant (some may be ahead/behind during rollout).
- Canary strategy: migrate one tenant first, verify, then batch.
Testing multi-tenancy
- Isolation tests: Create 2+ tenants, write data as tenant A, verify tenant B cannot see it.
- Context tests: Verify tenant context survives async operations (background jobs, event handlers).
- Edge cases: What happens with no tenant context? (Must fail closed, not open.)
- Performance: Verify RLS does not degrade query performance significantly (check EXPLAIN).
- All environments must have multiple tenants configured (including development).
Self-check before task completion
1---2name: multi-tenancy-patterns3description: Skill — Multi-Tenancy Patterns4---56# Skill — Multi-Tenancy Patterns78## When this skill activates9Any task involving multi-tenant architecture, tenant isolation, shared database10strategies, tenant context propagation, or tenant provisioning workflows.1112## Mandatory actions when this skill is active1314### Before designing multi-tenancy151. Assess isolation requirements (regulatory, security, performance).162. Determine tenant scale (10 tenants vs 10,000 tenants).173. Choose isolation level based on business requirements, not engineering convenience.1819### Isolation levels2021**Shared Database + Row-Level Security (RLS):**22- Single database, single schema, tenant_id column on every table.23- Cheapest to operate, easiest to deploy.24- Least isolated — bugs can leak data between tenants.25- Best for: SaaS with many small tenants, cost-sensitive.26- Risk: a missing WHERE clause exposes all tenant data.2728**Schema per tenant:**29- Single database, separate schema per tenant.30- Moderate isolation and moderate cost.31- Migrations must run per-schema (automation required).32- Best for: moderate tenant count (< 1000), need logical separation.3334**Database per tenant:**35- Completely separate database per tenant.36- Maximum isolation, maximum cost.37- Independent scaling, independent backup/restore.38- Best for: enterprise customers, regulatory requirements, high-value tenants.3940### RLS implementation (PostgreSQL)4142```sql43-- Enable RLS on the table44ALTER TABLE orders ENABLE ROW LEVEL SECURITY;4546-- Create policy that filters by tenant47CREATE POLICY tenant_isolation ON orders48 USING (tenant_id = current_setting('app.current_tenant')::uuid);4950-- Force RLS even for table owner51ALTER TABLE orders FORCE ROW LEVEL SECURITY;5253-- Set tenant context at connection level54SET app.current_tenant = 'tenant-uuid-here';55```5657**Critical rules:**58- RLS policies must be impossible to bypass from application code.59- NEVER use a superuser connection that bypasses RLS in application code.60- Test RLS with a dedicated "tenant leak" test suite.61- Add a CHECK constraint: `tenant_id IS NOT NULL` on every tenant-scoped table.6263### Tenant context propagation6465**Middleware pattern:**66```67Request → Extract tenant → Validate → Set context → Handler → Response68```6970**Extraction sources (priority order):**711. JWT claim (most secure — server-signed).722. Subdomain: `tenant1.app.com` → tenant_id lookup.733. Path prefix: `app.com/tenant1/...` → extract from URL.744. Header: `X-Tenant-ID` (for service-to-service only).7576**Propagation through the stack:**77- HTTP layer: middleware sets tenant in request context.78- Service layer: receives tenant from context, passes to repositories.79- Data layer: sets session variable before executing queries.80- Background jobs: serialize tenant_id in job payload, restore before processing.8182### Tenant routing8384**Subdomain routing:**85- `tenant1.app.com`, `tenant2.app.com`86- Requires wildcard DNS and TLS certificate.87- Clean separation, easy to identify tenant.88- Custom domains: CNAME tenant1.com → tenant1.app.com.8990**Path-based routing:**91- `app.com/tenant1/dashboard`, `app.com/tenant2/dashboard`92- Simpler DNS/TLS setup.93- Requires path prefix stripping in routing layer.9495**Header-based routing (internal only):**96- `X-Tenant-ID: tenant-uuid`97- For service-to-service communication within the platform.98- Never expose to end users (spoofing risk).99100### Tenant provisioning101102**Onboarding workflow:**1031. Create tenant record (name, plan, config).1042. Provision resources (schema/database if applicable).1053. Run seed data (default roles, settings, sample data).1064. Configure DNS/routing (subdomain or custom domain).1075. Send welcome notification.108109**Automation requirements:**110- Provisioning must be fully automated (no manual steps).111- Must complete in < 30 seconds for shared DB, < 5 minutes for isolated DB.112- Rollback on failure — no half-provisioned tenants.113114### Tenant-aware migrations115116**Shared DB (RLS):**117- Standard migrations — all tenants share the schema.118- Add tenant_id to new tables, backfill existing tables.119120**Schema per tenant:**121- Migration runner must iterate all tenant schemas.122- Parallel execution for speed, with error handling per-tenant.123- Failed migrations must not block other tenants.124125**Database per tenant:**126- Migration runner connects to each tenant DB.127- Version tracking per-tenant (some may be ahead/behind during rollout).128- Canary strategy: migrate one tenant first, verify, then batch.129130### Testing multi-tenancy131132- **Isolation tests:** Create 2+ tenants, write data as tenant A, verify tenant B cannot see it.133- **Context tests:** Verify tenant context survives async operations (background jobs, event handlers).134- **Edge cases:** What happens with no tenant context? (Must fail closed, not open.)135- **Performance:** Verify RLS does not degrade query performance significantly (check EXPLAIN).136- **All environments** must have multiple tenants configured (including development).137138## Self-check before task completion139- [ ] Did I follow the mandatory actions for this skill?140- [ ] Did I apply the patterns appropriate to the context?141- [ ] Did I verify the implementation meets the criteria above?142- [ ] Did I document decisions and trade-offs made?