DEV Community

Mark F A for RapidNative

Posted on

I Replaced Docker Supabase With a 58 MB Binary and Saved 1.5 GB of RAM

TL;DR

  • Docker-based Supabase local dev quietly costs gigabytes of RAM, minutes of boot time, and a background VM you forget is running until your fans remind you.
  • I swapped it for a single native binary: real Postgres 17, Supabase-compatible API surface, no Docker anywhere. The executable is 58 MB (92 MB installed).
  • On my machine, Activity Monitor gave back roughly 1.5 GB. Your numbers will vary; the shape of the win won't.
  • Not everyone should switch. If your local setup mirrors a self-hosted production stack container-for-container, stay where you are.

The state of local dev in 2026

Somewhere along the way, "run the database locally" turned into "run a container orchestrator locally." The standard Supabase local setup is a Docker Compose stack: Postgres, the auth server, the REST layer, realtime, storage, the studio UI, an API gateway. Each one is a container. On macOS and Windows, all of them live inside a Linux VM that exists solely so containers have somewhere to be.

None of this is wrong. It's a faithful replica of the hosted product, and for some teams that fidelity is exactly the point. But it's worth saying out loud what we normalized: to write a SELECT statement against a local database, my laptop was running a virtual machine.

Why Docker's cost is invisible until it isn't

The tax hides because you pay it in three currencies you don't track:

  • RAM you never see itemized. The VM reserves memory whether containers are busy or idle. It doesn't show up as "Supabase" in your process list; it shows up as your machine feeling smaller than it is.
  • Boot time you've stopped noticing. Pull images, start the VM, health-check seven services into readiness. You've built a coffee ritual around it, which is how you know it's too long.
  • A failure mode that isn't yours. When local dev breaks, the bug report is rarely about your schema. It's the VM not starting, a port collision with a container you forgot, an image version drifting from your teammate's. You end up debugging the harness instead of the app.

The moment it stops being invisible is always the same: you're on battery, on a call, with the stack "idle" in the background, and the fans spin up anyway.

The switch: what changed on my laptop

I moved my local stack to Tinbase, a single-binary, MIT-licensed local dev runtime that speaks the Supabase API surface and runs real Postgres 17 underneath. No containers, no VM, no compose file. The binary is 58 MB; the full install footprint on disk is 92 MB. (Worth being precise, because those two numbers get conflated constantly, including by us.)

The before state, for context, is the standard compose stack everyone recognizes:

# the shape of what I deleted
services:
  db:        # postgres
  auth:      # gotrue
  rest:      # postgrest
  realtime:
  storage:
  kong:      # api gateway
  studio:
Enter fullscreen mode Exit fullscreen mode

The after state is one process in my process list. My app code didn't change: it's the same connection string pattern and the same client SDK talking to a local URL. That's the whole trick of keeping the Supabase-compatible surface; the swap happens under the app, not in it.

Two honest notes on the migration itself. First, I treated it as a local-dev swap only; production still runs where it always ran. Second, I re-ran my migrations from scratch against the fresh Postgres 17 instance rather than trying to transplant a data directory out of a container volume. Cleaner, and it doubles as a test of whether your migrations actually stand alone.

The numbers

I'll give you the one number I actually measured and stand behind: about 1.5 GB of RAM back, per Activity Monitor, comparing my machine with the Docker stack idle versus the native binary idle. That lines up with what we found when we broke down why Supabase local dev needs 2.3 GB of Docker in the first place: most of the cost was never the database.

Boot time went from "start the stack, go do something else" to "it's a native process; it's just on." CI is the same story in a different costume: the job that used to pull and boot images now downloads one binary and runs. I'm deliberately not quoting you a seconds-saved figure for CI because it depends entirely on your runner's cache behavior. Measure yours; the direction is not in doubt.

One benchmark I'd push back on if you see it anywhere, including from me: synthetic query throughput comparisons. It's real Postgres 17 in both cases. The win is everything wrapped around the database, not the database.

What broke, what didn't

Didn't break:

  • Schema migrations. It's Postgres 17; they ran unmodified.
  • The client SDK. Same calls, local URL.
  • Auth flows in dev, RLS policies, the day-to-day query loop.

Needed attention:

  • Anything in my scripts that assumed Docker existed: docker exec for a psql shell, container-name health checks in a Makefile. All replaceable with direct equivalents, but grep for docker in your repo before you switch, because those assumptions hide in tooling, not app code.
  • Scheduled jobs and edge functions are the two areas where I'd tell you to verify behavior against your own use case before leaning on them, rather than take a blog post's word for it, mine included. Run your actual workload for a week locally before you delete the compose file for good.

Genuinely different: the debugging experience. When something misbehaves, there's one process and one log. No "is it the container, the network bridge, or the VM" triage tree.

Who should not switch

Honesty section. Stay on Docker if:

  • Your production is self-hosted Supabase in containers and you want local to mirror it container-for-container. Fidelity to prod is a legitimate reason to pay the tax.
  • You depend on studio-adjacent tooling in your daily loop and your team's muscle memory is built around the full stack UI.
  • Your team standardized on devcontainers for everything. One native binary inside a containerized-everything policy is friction, not simplification.
  • You need multi-service parity tests locally, where the gateway and service boundaries are part of what you're testing.

If none of those describe you, and for most app developers using Supabase as a hosted backend they don't, you're paying a replication-fidelity tax for fidelity you never use.

Where the ecosystem is going

The bigger pattern is that local dev is swinging back toward native. We spent a decade making environments reproducible by making them heavier, and the pendulum is now returning with the reproducibility kept: single static binaries, real databases instead of mocks, the production API surface without the production topology. SQLite's renaissance is part of it. Single-binary Postgres wrappers are part of it. "Compatible surface, native process" is a shape you're going to see a lot more of.

Docker won because it made "works on my machine" a solvable problem. The next round is making it solvable without the VM.

Have you measured what your local stack actually costs while idle? Open your resource monitor right now with everything "not doing anything" and drop the number in the comments. I have a theory the median is worse than anyone thinks.

Top comments (0)