# Multi-tenancy

Applies when: more than one customer's data lives in one system, and one of them asking
"can anyone else see this?" needs a better answer than "we're careful".

## The decision that determines everything after it

Shared schema with a `tenant_id` column, or a database per tenant.

There is no universally right answer, and anyone who tells you otherwise is selling
something. The honest framing is: **what does a leak cost you?**

## Shared schema

Cheaper, faster to build, simpler to deploy. Your isolation guarantee is then only as good
as every query anyone writes, forever.

One missing `where tenant_id = ?` is a data leak. Global scopes help enormously and are not
a guarantee — raw queries, joins, and reports written under deadline all bypass them.

Right choice when: tenants are low-sensitivity, you have many small ones, and the blast
radius of a leak is embarrassment rather than a lawsuit.

## Database per tenant

Costs real, ongoing effort, and you will feel it on every deploy:

- **Migrations fan out.** Two hundred tenants means two hundred migration runs, and one
  that fails halfway leaves you with a split-brain schema. Make them idempotent and make
  the runner resumable before you have two hundred tenants, not after.
- **Connection handling gets harder.** Connection pools, per-request switching, and queued
  jobs that must remember which tenant they belong to.
- **Cross-tenant reporting becomes a real project** rather than a `group by`.

What it buys is that "can another customer see our data?" has an **architectural** answer.
Not a policy answer. Not a code-review answer. The query cannot reach the other database
because it is not connected to it.

Right choice when: your customers are accountable for the data they hold, procurement will
ask, and the honest answer has to survive being checked.

## The part people forget: files

Database isolation with a shared filesystem is not isolation. If tenant uploads share a
disk, a path-traversal bug or a mis-scoped signed URL crosses the boundary the database
carefully maintained.

On one platform each tenant got its own cPanel account: separate database, separate file
storage, separate everything. Uploads never share a disk with another tenant's uploads.

## Queues, cache and the leaks nobody tests

Tenant context leaks most easily where you are not looking:

- A queued job that resolves the tenant from a request-scoped singleton runs against
  whichever tenant happened to be last.
- A cache key without a tenant prefix serves one tenant's data to another. This one is
  quiet, and it is the one that gets found in production.
- A scheduled command runs with no tenant context at all, so it either does nothing or does
  it to everyone.

Test the leak, not the happy path. A test that asserts tenant A sees their data proves
nothing. Assert tenant A **cannot** see tenant B's.

## Provisioning

Manual onboarding does not scale, and every manual step is a chance to misconfigure the
isolation you just paid for.

Automate it end to end: account, database, deployment, master data, DNS. Make every step
idempotent so a partial failure is a retry rather than a support ticket and someone
unpicking state by hand.

On one platform this took onboarding from a manual process to roughly thirty minutes with
no human step.

---
MIT licensed. Written by Smit Desai — <https://laravel.org.in>
