sbarbase
Contents

Architecture

What it is

The existing legacy-shared topology uses one gateway to check API keys and forward requests to the original Supabase services of the right environment. Each environment has its own database, Auth and REST; PostgreSQL itself and Storage are shared processes. The diagrams and sharing rationale below describe that topology.

The experimental native-dedicated placement contract records an environment's own engine, application database postgres, and a distinct maintenance database with separate credential references. The catalog is the declaration authority; discovering an engine does not authorize its adoption. Every native declaration currently resolves as unadmitted, so declaring it does not activate provisioning, routing, service consumers, pools, recovery or external effects. See the native placement contract and gauntlet ledger.

Why

The choice is to share the heavy engines (PostgreSQL, Storage) and keep separate the services that hold an environment's identity and API surface (Auth, PostgREST, the database and its logins). Auth and PostgREST are small, and running them per environment means no request-time database switching and no fork of either service.

Rejected alternatives:

How we built it

Compared with a full stack per project, Sbarbase shares one gateway, one PostgreSQL engine and one Storage process, and runs Auth and REST per environment

PostgreSQL and Storage run once; each environment adds its own database, a Storage tenant and a small Auth and REST.

A request goes from the app through the TLS proxy to the gateway, which checks the key for the environment named in the path and refuses at once with 429 or 503 instead of queueing, then to that environment's Auth or REST and its own database login; files go to shared Storage with a tenant header

The gateway checks every request against the environment named in its path, and refuses rather than queues.

The full routing, including the management path and the provisioning worker:

The app and the operator browser reach the TLS proxy, then the loopback server, which routes by path: /management/auth/v1/* to the management Auth realm, /management/* to the control API and its SQLite catalog, and an environment's auth, rest or storage path to the gateway; the gateway forwards to that environment's Auth or PostgREST or to shared Storage, all on the environment database in the shared PostgreSQL engine; the provisioning worker reads the catalog and runs the pinned containers

One proxy, one loopback server, three paths.

Request path. An app calls /<environment>/rest/v1/... with its publishable key; a server may send a secret key instead. The gateway reads the environment's routing record (is it in maintenance? where does it live?), verifies the key against stored hashes, admits the request if the environment has fewer than its concurrent limit in flight, caps the body at 1 MiB (a Storage file upload streams through up to the upload limit, 50 MiB by default) and forwards it to that environment's own PostgREST. Storage requests go to the one shared Storage process with a trusted tenant header naming the environment. Realtime sockets and Edge Functions calls take the same path to that environment's own containers. Without an API key the gateway passes only public and signed Storage downloads, the Auth steps a browser reaches by a link (verify, authorize, callback) and Edge Functions deployed with verify_jwt off.

Management path. Operators log in against a dedicated management Auth realm that is never an application environment's Auth. The control API derives the actor from that login, never from the request body, and applies owner, admin and viewer roles. Agents reach the same control API through /v1 and /mcp with a personal access token, acting as the person who made it (AI agents).

Trust boundary. Only the TLS proxy faces the network; the loopback server and every container behind it listen on loopback or internal Docker networks, with no published ports. App traffic enters only through the gateway and needs an API key of that environment; operator traffic goes only through the separate management realm and the control API. The server's operators are trusted, the visitors of the apps are not. What that means for shared components is in isolation and trust.

Background work. Creating an environment queues a job. A single provisioning worker, holding an exclusive lock, creates the database and scoped logins, writes the connection rules and starts the services. See provisioning.

Shared Separate per environment
PostgreSQL engine (one cluster) Database, three scoped service logins, connection rules
Storage process Storage tenant, its signing keys and metadata
Gateway and control API Auth, PostgREST, publishable and secret keys, routing record
Vault's root key Realtime, Edge Functions and Studio containers, when turned on
Host CPU, memory, disk Resource tier limits per container

Code: src/control/application.ts (routing between the three paths), src/gateway/handler.ts and src/gateway/managed.ts (gateway), src/gateway/concurrency.ts (admission), src/control/http.ts and src/control/key-http.ts (management API), src/control/catalog.ts (catalog), lab/worker.py and lab/durable_runtime.py (runtime). Routes are listed in the API reference.

Limits

Go deeper

Edit on GitHub