DEV Community

Russel Dsouza
Russel Dsouza

Posted on

Self-Hosted Supabase Doesn't Break Because Postgres Is Hard

I spoke to teams who had run self-hosted Supabase in production. Some still do. Some moved back to hosted. A couple ended up assembling their own stack from primitives.

The question I wanted answered: is the pain "Postgres is hard", or something more specific?

Disclosure before anything else: I work on a Supabase-compatible runtime, so I had a stake in one of the possible answers. The findings below happen to point at a product shape close to what my team builds, and you should weigh them accordingly. I've tried to report what people said rather than what would be convenient.

  • Nobody complained about Postgres. Every team described it as boring and working.
  • The pain is the surrounding services: auth, realtime, storage, proxy, meta, and the rest
  • Version drift between local and production was named unprompted by every team
  • Realtime dropping events during deploy restarts was the second most common
  • Both are distribution problems, not database problems

The three failure modes

Ranked by how many teams raised each without prompting:

  1. Auth service version drift between local and production. Every team.
  2. Realtime falling over during deploy churn. Most of them.
  3. Storage service behaving differently from S3 behind a reverse proxy. Several.

Postgres was described as boring and working, by everyone. Not one complaint.

The pain lives in the distribution: the wrapping services that turn a database into a platform. Auth, realtime, storage, functions, image proxy, gateway, metadata. Roughly ten moving parts, each with its own version, its own restart behaviour, and its own proxy quirks.

Version drift, in detail

This came up in every conversation.

The shape: you develop locally against current service versions. Production runs older ones, because upgrading is a maintenance window and there is always a reason to defer it. Then code that works locally fails in production, because something changed between those versions.

Error taxonomies are the usual culprit. An auth error name that exists in the version on your laptop does not exist in the version on your server, so the client falls through to a generic unknown-error path and tells you nothing useful.

One team described losing a full day to exactly this. The conclusion they reached, which several others reached independently: nothing about that day was Postgres.

The workaround everyone lands on is pinning local development to the same service versions as production. Which means every developer runs deliberately outdated services, nobody benefits from upstream fixes, and onboarding a new engineer means reproducing a bespoke pinned setup that exists in one person's head.

That is a distribution problem. Not a code problem, and not a database one.

Realtime during deploys

The second most common, and the more insidious of the two, because it produces no errors.

Rolling deploy. The realtime service restarts. WebSocket clients disconnect, then reconnect, which works correctly. But during that reconnect window, subscriptions can miss changes.

Nobody sees an error. Users just don't get updates. The team finds out days later, when a customer mentions that a change on one device never showed up on another.

The workaround: deploy realtime only in low-traffic windows and manually verify subscription counts either side. That is toil, and it scales with team size in the wrong direction.

Storage behind a proxy

Less universal, sharper when it lands. Several teams lost an afternoon to 403s on presigned uploads that worked locally and failed behind nginx or HAProxy.

The cause was some combination of header case sensitivity and a Host header mismatch. Straightforward once you know where to look, and genuinely baffling if you assumed "S3-compatible" means "behaves identically to S3 in every deployment topology."

It doesn't. Compatibility claims describe an API surface, not a network path.

What this actually implies

The useful finding isn't that self-hosting is hard. It's that the difficulty is misattributed.

Self-hosted Supabase doesn't fail because a database is hard to run. It fails because a platform assembled from around ten independently versioned services has a coordination problem, and coordination problems don't announce themselves. They present as a weird error in production, an event that didn't arrive, a 403 that only happens behind the proxy.

Two things would remove most of what these teams described:

Ship every service at one version, by construction. Not tooling that makes alignment easier. A shape where misalignment isn't expressible, because the whole platform is one artifact with one version.

Make realtime restarts transparent. Resumption plus event replay across the disconnect window, so a deploy stops being a silent data-loss event.

Proxy compatibility is a third, and it becomes essential rather than nice-to-have the moment anyone puts this behind enterprise networking.

I'll be straightforward about the conflict here: the first of those describes tinbase, which is what I work on. It is a single binary, so services can't drift apart. It does not solve the second, and I'm not aware of anything that does yet.

It's also a local development runtime rather than a production self-hosting replacement, which is worth stating plainly given that everything above is about production.

If you're running it now

If you're hitting these, you aren't doing anything wrong. The shape of the distribution is doing it, and every workaround people described was a reasonable response to an unreasonable coordination problem.

Pin your versions deliberately rather than accidentally. Write down which ones, somewhere a new engineer will find. Deploy realtime when it costs least. And treat "S3-compatible" as a claim about an API, not a promise about your network.


If you run self-hosted Supabase in production, what broke that wasn't the database? I'm collecting these, and my expectation is that almost none of the answers are Postgres.

Top comments (0)