Skip to main content

Command Palette

Search for a command to run...

Benchmarking Local Postgres for Supabase Devs: Docker vs. PGlite vs. tinbase

Cold boot, idle RAM, and query throughput measured across three local Postgres runtimes, plus a compatibility matrix for picking the right one.

Updated
7 min readView as Markdown

Table of contents

  1. Why local Postgres matters for Supabase devs

  2. The three contenders

  3. Test methodology

  4. Results

  5. Compatibility matrix

  6. When to pick which

  7. What's next

Why local Postgres matters

Every Supabase-compatible dev workflow depends on a local Postgres. What differs is how heavy that Postgres runtime is on your laptop.

The supabase start docker-compose stack is the reference implementation: 12 containers, roughly 2.3 GB of install footprint, and around 1.6 GB of memory under load. It is production-parity by design, and you pay for that parity in resident memory every hour your editor is open.

Two lighter alternatives have appeared. PGlite compiles Postgres to WASM and runs it in-process. tinbase packages real Postgres plus auth, storage, and realtime into a single executable.

Each makes different tradeoffs. This post benchmarks all three on a common workload so you can pick deliberately rather than by default.

The interesting question is not which one is fastest. It is which one is fast enough while leaving you enough RAM to run the rest of your machine.

The three contenders

Docker Supabase. The official local stack: Postgres, Studio dashboard, GoTrue auth, Realtime, Storage, the Edge Functions runtime, and a Kong gateway, across 12 containers on docker-compose. Exact parity with hosted Supabase is the whole point.

PGlite. Postgres compiled to WASM, roughly a 3 MB bundle, running in-process in Node, Deno, or the browser. No auth, no realtime, no storage. Just SQL. Ideal for tests, notebooks, and offline-first apps that need a genuine Postgres in the browser rather than a SQL-shaped imitation.

tinbase. A single executable that runs embedded Postgres 17 alongside GoTrue-compatible auth, the Storage API, realtime, and edge functions in one process. The official supabase-js SDK points at it unchanged. MIT licensed, and currently at v0.14.0 alpha, which the project describes as suited to local development, prototypes, and embedded or browser use rather than production.

One nuance worth flagging before the numbers: these three are not cleanly separate. tinbase ships multiple database engines, and one of them is PGlite. The WASM engine is how tinbase runs inside a browser tab. So when this benchmark compares PGlite against tinbase, it is really comparing bare PGlite against tinbase's native embedded Postgres engine, not two unrelated projects.

Starting tinbase is a single command, and it serves on the same port the Supabase CLI uses:

npx tinbase start
# supabase-js connects at http://127.0.0.1:54321

Test methodology

Hardware: MacBook Air M2, 16 GB RAM, macOS 15.3.

Schema: a small e-commerce dataset. Four tables (users, products, orders, order_items) totalling 12 columns. Seeded with 10k users, 1k products, 50k orders, and 200k order_items.

Workloads:

  1. 1,000 sequential inserts into orders

  2. 1,000 sequential selects joining users to orders

  3. 100 concurrent mixed inserts and selects

Metrics: cold boot time, idle RAM after boot, workload latency (median and p99), and disk footprint. Every measurement was run three times with the median reported.

Results

Metric Docker Supabase PGlite tinbase
Cold boot time 44 s 0.3 s 2.5 s
Idle RAM 1,626 MB 42 MB 98 MB
Disk footprint 2.3 GB 3 MB 58 MB
1,000 inserts (median) 480 ms 620 ms 510 ms
1,000 selects with join (median) 210 ms 380 ms 240 ms
100 concurrent mixed (p99) 180 ms N/A (single-writer) 210 ms
Handles concurrent writes Yes No Yes

Docker Supabase takes raw throughput by a slim margin on selects. Its Postgres runs on native Linux inside the Docker VM, which edges out tinbase's macOS-native build. That margin is 30 ms on a 210 ms query, against a 16x difference in resident memory.

PGlite is meaningfully slower on complex queries, which is the expected cost of WASM, and it cannot handle concurrent writers at all. Its WAL is single-threaded, so workload three simply does not apply.

A 14 percent latency penalty in exchange for 1.5 GB of reclaimed RAM is not a close call on a 16 GB laptop.

Compatibility matrix

Feature Docker Supabase PGlite tinbase
Standard Postgres SQL
Row-Level Security
supabase-js auth
Realtime subscriptions
Edge functions
Storage buckets
Studio web dashboard
Concurrent writers

The Studio row deserves a note, because it is the row most people get wrong about tinbase. A Supabase-Studio-style dashboard ships in the binary at /_/, covering the table editor, SQL, auth, RLS policies, storage, and live logs. It is not a separate process and it is not something you install. If you assumed single-binary meant dropping to psql, that assumption is out of date. The tinbase Studio tour shows the current surface.

On RLS specifically, all three behave the same way for policy evaluation: requests run with your JWT claims applied, so auth.uid() policies work without modification. That is the compatibility detail that matters most in practice, because RLS is where local-versus-hosted divergence usually bites.

Extension support is where the matrix gets fuzzier than a checkmark can express, so verify against the docs for whichever extension your project actually depends on. Worth knowing: tinbase implements database webhooks, cron scheduling, and a queue subset natively, so several things you would reach for pg_net, pg_cron, or pgmq to do on hosted Supabase do not need an extension at all.

When to pick which

Docker Supabase. Pick it when you need production parity for release-week testing, when your team is large enough that everyone shipping to hosted Supabase should be testing against the identical stack, or when you live in Studio and do not want to context-switch. Parity is a real feature and the memory cost buys something.

PGlite. Pick it for tests that need a real Postgres per test run, or for offline-first apps that ship a Postgres to the browser. It is not a general-purpose dev database, and trying to use it as one means discovering the single-writer limit at an inconvenient moment.

tinbase. Pick it when you want Supabase dev ergonomics without paying 1.6 GB of idle RAM. It is strongest on machines with 16 GB or less, and for demos and workshops where you cannot assume Docker is installed on anyone's laptop. Because migrations are read from supabase/migrations/*.sql and tracked in the same table the Supabase CLI uses, the exit path is intact: outgrow it and push the same files to hosted Supabase.

The alpha status is the honest caveat. At v0.14.0 the project is explicit that it is not production-ready, so treat it as a local dev and prototyping tool and keep your production path pointed elsewhere for now.

What's next

All three are converging on the same idea from different directions: local development does not need to look like production infrastructure to be useful.

Docker Supabase is unlikely to shed its container footprint, because that footprint is the parity guarantee. PGlite is pushing further into the browser. tinbase is working toward production readiness, and the direction to watch is whether the single-process model holds up once real deployment pressure lands on it. The published footprint benchmarks are reproducible from the repo if you want to run them on your own hardware rather than trust mine.

My own workflow settled on tinbase for everyday development and Docker Supabase for release-week testing. Yours will vary with team size and how much time you spend in Studio.

If you run this benchmark on different hardware, particularly on Linux where the Docker VM penalty disappears, the select-throughput gap should narrow. I would be curious to see those numbers.