Multi-Tenancy & Isolation
A single AnvilBase deployment hosts many projects, and each project is completely isolated from the others. Isolation isn’t a feature bolted on top — it’s the architecture.
The isolation matrix
| Resource | Isolation strategy |
|---|---|
| Database | a separate database platform_<project_id> (the project UUID with hyphens removed — 32 hex chars) — not a schema, not row tags |
| Storage | a separate bucket bucket-<project_id> with its own SSE encryption key |
| Secrets | an encrypted vault inside the project DB, with a unique key |
| Auth | a unique JWT secret per project (independently rotatable) |
| Edge functions | a separate OS process per project, each filesystem-scoped to /functions/<project_id> only — see Functions runtime |
| Cache | a per-project Valkey ACL user proj_<project_id> physically confined to the <project_id>:* keyspace — the data plane connects as that user — see Cache / KV |
| Queues | a PGMQ namespace inside the project DB |
Because each tenant is a separate database, the strongest isolation boundary Postgres offers is in play — a query in one project literally cannot name another project’s tables.
Two enforcement points
Isolation is enforced before a request reaches a service, and again inside the database:
- Routing layer (control plane). The credential identifies exactly one project. The control plane resolves that project and routes only to its database pool, its bucket, its cache namespace, its queue namespace. A credential for project A can never address project B’s resources.
- Database (RLS). Within a project, Row Level Security scopes rows to the requesting user/role.
IDOR safety
Per-row management queries are scoped by project_id. Trying to access another
project’s resource by guessing its UUID returns 404 Not Found, not 403 —
the existence of the resource isn’t even revealed. This applies across webhooks,
secrets, users, functions, and the rest of the management surface.
Cryptographic separation
- Each project has its own JWT secret, so a token minted for one project is meaningless to another (signature verification fails).
- Each storage bucket has its own SSE-S3 key.
- The secrets vault uses encryption keyed so that one project’s secrets can’t be decrypted in another’s context.
Rotating one project’s JWT secret or keys has no effect on any other project.
What’s shared (and why it’s safe)
A few components are physically shared but logically partitioned:
- One Postgres server hosts many databases — isolation is at the database level, the boundary Postgres is designed to enforce.
- One Valkey instance — but each project’s data plane connects as its own
ACL user (
proj_<project_id>) that is physically confined to the<project_id>:*keyspace, so the partition is enforced by Valkey, not just by a key prefix. Use the per-project flush endpoint, never a global flush. - One control plane binary — but it holds per-project pools and injects per-project context on every request.
- Raw SQL (SQL Editor /
exec_sql) runs on a least-privileged per-connection role that can act asservice_roleinside the project’s own database only. It is not the database owner and has no cross-database capability (nodblink, noCREATEROLE/CREATEDB), so a project’sservice_rolekey cannot reach another project’splatform_<id>database. - One storage backend — but buckets and keys are per-project.
This sharing is exactly what makes AnvilBase efficient (tens of MB per project instead of hundreds), without weakening the boundary.
Resource fairness
Per-project quotas
(max_db_size_mb, max_storage_size_mb, max_edge_function_count) and per-scope
rate limits prevent one noisy project from
starving others. Storage writes over quota return 429; rate-limited requests
return 429 with Retry-After.
Verifying isolation
- The deployment ships isolation-boundary tests as part of its security posture (cross-tenant read/write attempts must fail).
- Operators can confirm separation directly: each project’s data lives in its own
platform_<id>database (\linpsql) and its ownbucket-<id>.
Operator responsibilities
Isolation inside AnvilBase is automatic, but the deployment still needs:
- Encryption at rest on the Postgres and storage volumes (Encryption).
- Network restriction so only Traefik is public (Network Security).
- Strong deployment secrets and protection of the platform database (the root of all per-project crypto).
See the Production Checklist.
Next: RBAC & Team Management.