TENANT-FORGE SaaS · Multi-Tenancy 2013 — 2015

One Click, One Tenant, Zero Humans

Multi-tenant SaaS where a new institution gets its own subdomain and a fully configured stack automatically.

ROLE // Architect and lead server programmer

1 click Signup to live subdomain
0 Manual steps in provisioning
4 REST backends alongside, on GAE
STACK // PythonDjangoRailsPostgreSQLRedisCeleryNginxAWS

⬢ Client-anonymised by design. No employer, product or customer is named, and no proprietary code appears here — only the system shape and the reasoning behind it.

Provisioning as a state machine, not a script. Every step idempotent and resumable. ONBOARDING CONTROL PLANE APP TIER DATA OPERATIONS Signup institution picks a name Provisioner state machine DNS Automation subdomain record Config Generator vhost + app config Certificate Issuance Schema & Seed tenant baseline Shared App Fleet no per-tenant deploys Tenant Resolver host → tenant, per request Tenant-Scoped Data isolation boundary Tenant Registry shared metadata Post-Provision Checks prove it works Backups & Cron Celery + Redis
interface service state async external Provisioning as a state machine, not a script. Every step idempotent and resumable.

The bottleneck nobody budgets for

Selling software to schools and colleges means selling to a lot of small institutions rather than a few large ones. That’s a fine business — right up to the point where onboarding each one takes a person half a day. Then your growth ceiling is your ops headcount, and it’s an unusually expensive ceiling because the work is boring.

The goal was blunt: an institution signs up, picks a name, and a minute later has a working, branded, isolated instance at their own subdomain. No ticket, no engineer, no waiting.

Constraints that shaped the design

Isolation is a one-shot decision. Shared schema with a tenant column, schema-per-tenant, database-per-tenant — each has a different cost curve, and migrating between them once you have live customers is a project, not a refactor.

Provisioning touches things that fail independently. DNS propagates on its own schedule. Certificate issuance depends on a third party. Schema seeding depends on your own database. A script that runs these in sequence and dies halfway leaves a tenant that exists in some systems and not others — the worst possible state.

Everything ran on infrastructure I also operated. Gunicorn behind Nginx, Supervisor for process control, Celery and Redis for async, cron for scheduling, on AWS. Provisioning had to fit that reality rather than assume a platform that would do it for me.

Decisions and trade-offs

Provisioning as a resumable state machine

The critical reframe. Not “run these eight steps” but “this tenant is in state awaiting_dns; advance it.” Every step is idempotent, every step records its outcome, and the whole thing can be re-driven from any point without duplicating work.

This costs noticeably more to build than a script. It buys the thing that actually matters: a partial failure is a tenant that is behind, not a tenant that is broken. Retry becomes safe, which means retry can be automatic, which means most failures resolve without anyone finding out.

Shared fleet, per-request tenant resolution

No per-tenant deployments, ever. One application fleet, and the host header resolves to a tenant at the start of every request, with tenant scope applied from that point on. The alternative — an app instance per customer — is seductive because it feels isolated, and it means every deploy, every patch and every migration is multiplied by your customer count.

The trade-off is that tenant scoping must be enforced structurally, at the data-access layer, not by remembering to add a filter. One forgotten WHERE tenant_id = ... in a multi-tenant system is a cross-customer data leak. That’s the price of the shared fleet and it has to be paid in architecture, not in code review.

Verify the tenant works before declaring it ready

The final provisioning step wasn’t “mark active” — it was an automated check that actually exercised the new tenant end to end, and only then flipped the registry. It’s a small amount of extra work that converts a class of silent failures into loud ones, before a customer discovers them.

Automate DNS and certificates as part of the same machine

Subdomain creation and certificate issuance were steps in the state machine, not manual prerequisites. This is the piece most “one-click provisioning” implementations quietly omit, and it’s exactly the piece that requires a human at 9am on a Monday.

Boring async infrastructure, used consistently

Celery with Redis for asynchronous work, Supervisor keeping processes alive, cron for scheduled jobs, gunicorn behind Nginx. None of it novel. All of it understood, debuggable at 3am, and applied the same way everywhere so that a new service didn’t come with a new operational vocabulary.

What I’d do differently in 2026

Declaratively. Today I’d express the desired tenant state as data and run a reconciliation loop that drives reality toward it continuously, rather than a state machine that advances once at creation. The state-machine version handles provisioning well but has nothing to say about drift — a tenant whose config was hand-edited months later, or whose certificate quietly failed to renew.

I’d also make the isolation boundary configurable per tenant from the start. The shared-fleet, scoped-data model is right for the long tail of small institutions, but the one large customer who needs a dedicated boundary shouldn’t require a different product.