PostgreSQL · Solution

Multi-tenant SaaS backend

Isolate every tenant’s rows with PostgreSQL row-level security — one schema, one policy, no cross-tenant leaks.

Three patterns, one platform

Multi-tenant on Postgres comes in three flavors. RLS keeps everything in a single shared schema and uses policies to filter by tenant — cheap, simple, but harder to recover from a leaked policy. Schema-per-tenant gives each tenant its own schema in the same database — clearer isolation, easier per-tenant migrations, but breaks down past ~5,000 tenants. Database-per-tenant gives each tenant its own database (and on Luceris, its own credentials at the proxy) — strongest isolation, simplest disaster recovery, more databases to manage. We support all three; the right answer depends on your tenant count, blast-radius tolerance, and per-tenant data volume.

Per-tenant credentials

When you pick DB-per-tenant, each database has its own credentials enforced by bcrypt at the proxy. A leaked password compromises one tenant, not your whole fleet.

RLS-friendly defaults

Use SET LOCAL inside a transaction (the sample below) so the tenant variable can never leak between requests. We document the gotchas (search_path, RESET ROLE) so you do not learn them the hard way.

One database per tenant, one API

Create, rotate and delete tenant databases from the dashboard or the API; each has its own root user and connection string.

Per-tenant observability

An audit log keeps a trail of admin actions on every database. Per-database query stats via pg_stat_statements.

Code sample

sqlRLS pattern with a session GUC
-- Tables carry a tenant_id; policies filter by it.CREATE TABLE projects (  id          BIGSERIAL PRIMARY KEY,  tenant_id   BIGINT NOT NULL,  name        TEXT NOT NULL);ALTER TABLE projects ENABLE ROW LEVEL SECURITY; -- Policy reads the tenant from a session variable.CREATE POLICY tenant_isolation ON projects  USING (tenant_id = current_setting('app.tenant_id')::BIGINT); -- Before each request, your app sets the session variable.-- Use SET LOCAL inside a transaction so it cannot leak between requests.BEGIN;SET LOCAL app.tenant_id = '42';SELECT * FROM projects;COMMIT;

How it looks on Luceris

For RLS or schema-per-tenant, one Postgres database holds all tenants; your app encodes the tenant in a session variable. For database-per-tenant, you provision one database per tenant via the dashboard or the API; each database has its own credentials and its own connection string. Either way, the Luceris proxy validates credentials and routes traffic — your app does not see the underlying topology.

Examples

  • Small team, 50 tenants, low per-tenant data volume → RLS in a single shared database
  • Mid-stage SaaS, 1,000 tenants with diverging schemas → schema-per-tenant in a single database, with per-tenant migrations
  • Enterprise SaaS with regulated per-tenant isolation → database-per-tenant, one database per tenant

Frequently asked questions

Does RLS hurt performance?
Modest overhead — typically 5–15% on filtered queries — because Postgres applies the policy as a WHERE clause. Far less than the cost of getting it wrong with hand-rolled tenant filters in application code.
How do you handle per-tenant migrations?
In schema-per-tenant, you iterate schemas and run migrations one at a time (or in parallel). In DB-per-tenant, you script the migration across the database list. We expose a list-databases API to make scripting straightforward.
What about noisy tenants?
In a shared model, watch pg_stat_statements per tenant; move outliers to their own database with pg_dump and restore. In DB-per-tenant, the rest are unaffected.

Pick the isolation model that fits, not the one your vendor enforces.

Start on RLS in the Free tier; promote to DB-per-tenant when you need to. Luceris Cloud is for development environments and not for production.