The short answer
If clients need the same software with different data, build multi tenant. If they genuinely need different behaviour, build single tenant deliberately and price for it. What you must avoid is one codebase quietly forking per client, because every update then has to be repeated and tested many times.
That drift is the default outcome if nobody decides, because each individual exception seems reasonable.
The trade-offs
| Multi tenant | Single tenant | |
|---|---|---|
| Running cost | Lower, shared | Higher, per client |
| Updates | Once, everyone | Per instance |
| Data isolation | Logical, needs care | Physical, simpler to explain |
| Per-client customisation | Configuration only | Anything, at a cost |
| Noisy neighbour risk | Real | None |
| Compliance story | Harder to explain | Easier |
The compliance row is what drives single tenant in regulated sectors. Some clients will not accept their data sharing a database, whatever the isolation.
Multi tenant needs discipline
The risk is one client seeing another's data. That requires isolation enforced in one place rather than remembered in every query.
- Enforce the tenant filter at the data layer, not in each query
- Test explicitly that one tenant cannot reach another's records
- Include tenant context in every log line for debugging
- Watch for one tenant's load affecting others
- Be careful with anything cached across requests
The first point is the difference between a design and a convention. A convention will be forgotten in one query eventually, and that is all it takes.
Configuration rather than forks
Per-client differences belong in configuration: enabled features, branding, rules, templates. The moment a client needs a code change, you have started a fork.
Where a genuine code difference is unavoidable, keep it behind a feature flag in the shared codebase rather than in a separate branch. One codebase with flags is maintainable; many branches are not.
The expensive middle
- A client asks for something specific and it gets built into their copy.
- Another client asks for something else, and the copies diverge.
- An update now has to be applied and tested several times.
- Nobody can say which version each client is on.
- The cost per client rises with every client added, which is backwards.
This is the failure mode to design against. It arrives gradually and each step is defensible.
Deciding
Ask whether clients need different data or different behaviour. Different data is multi tenant. Genuinely different behaviour, for reasons that will persist, may justify single tenant, priced accordingly.
And ask what your clients will require contractually. Sometimes the answer is decided by their procurement rather than by your architecture.