The first real thing built on barakoCMS is a membership and treasury app for a members' club. Members, dues, statements, a double-entry ledger. Then a second club wanted the same thing. Then the question that decides your whole architecture: do I run a second copy, or one copy that holds both?
Running a copy per client is the easy answer and a slow death. Two databases, two deploys, two sets of migrations to keep in step, two things to back up, two of everything to patch when a security fix lands. By the time you're at five clients you're spending weekends being a sysadmin for infrastructure that does the same thing five times.
So barakoCMS is multi-tenant. One deployment, many tenants, data scoped per tenant. A club is a tenant. Its members, dues, and content live in its own partition and never bleed into another club's.
The flavor I went with is what Marten calls conjoined tenancy. Same tables, every row tagged with a tenant id, and the query layer adds the tenant filter for you so you can't forget it. That last part matters more than it sounds. The dangerous bug in any multi-tenant system is the one query where you left off the tenant condition and now club A can see club B's members. When the framework appends it, that whole category of mistake mostly goes away.
There's a boundary I had to draw carefully. Some things are global, not per-tenant. A user is one person even if they belong to two clubs. Roles are defined once. So users and roles are global, and the link between a user and a club (their membership, their role there) is what's tenant-scoped. Getting that line right took a couple of tries. Put too much in the global bucket and tenants leak into each other. Put too much in the tenant bucket and a person needs a separate account per club, which is miserable.
Tenant resolution is boring on purpose. A request carries a header or comes in on a subdomain, middleware reads it, everything downstream is scoped to that tenant. No login screen asks "which club?" The context is already decided by the time your code runs.
The test I care about most here isn't a feature test. It's the isolation test. Seed two tenants, log in as one, try to read the other's data, assert you can't. I run it every build. Multi-tenancy is one of those things where the feature working is the easy part, and the isolation holding is the actual product.