13 min read
These get discussed as if they were the same decision. They are not, and picking the wrong one wastes a month.
Self-hosting Supabase keeps everything about how your app works and moves the operations to you. Same anon key in the browser, same RLS-as-the-only-wall, same client-talks-to-database shape. You are changing who runs it.
Owning a backend puts a server of yours between the browser and the database. You are changing how it works.
If your problem is the bill or where the data lives, the first one solves it and the second is overkill. If your problem is that your application outgrew CRUD, the first one solves nothing.
Not one process. The Docker Compose stack runs roughly eight services: Postgres, PostgREST (the auto-generated API), GoTrue (auth), Realtime, Storage, an image proxy, Kong as the gateway, and Studio.
git clone --depth 1 https://github.com/supabase/supabase
cd supabase/docker
cp .env.example .env # generate real JWT secrets — never ship the samples
docker compose up -dUp in an afternoon. The afternoon is not the cost.
What you now own: upgrades across eight moving parts that version independently, Postgres backups and restore drills, TLS and the gateway, SMTP for auth emails, storage backing and its backups, and being the person paged at 3am.
What you keep, which is the point: the same client SDK, the same RLS policies, the same table structure. Application code barely changes. That is why it is the right answer for a whole class of teams.
What it genuinely fixes:
| Problem | Fixed? |
|---|---|
| Metered bill growing faster than revenue | ✅ — a VPS is a fixed number |
| Data must stay in a country or under your contract | ✅ |
| Vendor could change terms or pricing | ✅ |
| Connection limits at scale | ✅ — you size it |
| Client-side key model | ❌ unchanged |
| Business logic still lives in the client | ❌ unchanged |
| RLS is the only access wall | ❌ unchanged |
Supabase's model is a browser talking to a database through an auto-generated API, with Row-Level Security as the boundary. For a lot of applications that is genuinely enough, and pretending otherwise is snobbery.
It stops being enough at four specific points.
Multi-step operations that must be atomic. "Reserve stock, create the order, charge the card, send the email" is one unit of work — either all of it happens or none of it does. Split across client calls, a dropped connection between step two and three leaves you with an order nobody paid for. This is not a policy problem; the client is simply the wrong place for a transaction.
Permissions that are not row ownership. RLS answers can this user see this row. It answers badly: a sales rep may see the customer but not the margin column; a manager may approve up to €5.000; a support agent may refund within 30 days. Field-level and conditional rules can be forced into policies, but the policy becomes unreadable and unreviewable — and an access rule nobody can read is not a control.
Secrets that cannot be in a browser. The moment you integrate a payment provider, an ERP or a shipping API, the credential cannot live client-side. You need a server. Edge Functions are that server, arriving one at a time — at which point you have a backend, just an unplanned one.
Logic that must run even when nobody has the tab open. Scheduled jobs, retries, reconciliation, webhook processing that outlives the request.
If you recognise two or more of those, self-hosting will not help. You have an architecture question wearing an infrastructure costume.
| Stay on Supabase Cloud | Self-host Supabase | Own the backend | |
|---|---|---|---|
| Time to get there | — | an afternoon, then forever | 1-3 weeks |
| Ongoing ops | none | yours, eight services | yours, fewer parts |
| Application rewrite | none | ~none | the data layer |
| Cost shape | metered | fixed | fixed |
| Data residency | their regions | yours | yours |
| Transactions | client-side | client-side | server-side |
| Access control | RLS | RLS | whatever you need |
| Third-party secrets | Edge Functions | Edge Functions | normal |
| Good for | most apps, early | cost and residency | applications with a domain |
Your bill grew, everything else is fine. Self-host, or move to plain managed Postgres and keep the client SDK for auth. Do not rewrite your application to save money — that trade almost never pays.
A customer or regulator requires the data somewhere specific. Self-host. It is exactly what it is for.
You are writing your fourth Edge Function this month. You are already building a backend, badly, one function at a time. Stop and build it deliberately.
A bug let one customer see another's data. Do not conclude "RLS is bad" — first check whether your app connects as the table owner, because policies are silently ignored for the owner and that single misconfiguration accounts for a lot of these. Fix that before rearchitecting anything.
You cannot describe your permission rules as row ownership. Own the backend. Everything else is a workaround with a maintenance cost.
For a large share of apps that hit the bill problem, the right move is neither: managed Postgres from any provider, plus your own thin API, keeping whatever auth you already have.
You get a fixed cost, no eight-service stack to babysit, and a place to put transactions when you eventually need them. It is less exciting than either option above and it is frequently correct.
If you land on "own the backend", the kits ship that shape ready to run — Checkout Kit and Booking Kit: a standalone Go or Node server, field-level RBAC, transactions where they belong, and a database only your server can reach. And if you are moving an existing app, Moving off Supabase covers the order that keeps it running.