"Just run Postgres locally" hides a real decision. In 2026 there are four genuinely different ways to do it, and they differ by two orders of magnitude in weight. Picking wrong doesn't break anything; it just quietly taxes every git clone, every CI run, and every laptop on the team for the life of the project.
Here they are, lightest to heaviest, with the honest trade-offs.
1. Embedded: PGlite (~3 MB)
The newest option and the lightest by far. PGlite is Postgres compiled to WebAssembly, packaged as a library. You don't start a database, you import one:
import { PGlite } from '@electric-sql/pglite'
const db = new PGlite() // in-memory
// const db = new PGlite('./data') // or persisted to disk
await db.query("select 'hello' as message")
Under 3 MB gzipped, starts in milliseconds, persists to the filesystem or IndexedDB, and supports real extensions including pgvector. This stopped being a curiosity a while ago: it crossed 10 million weekly npm downloads this year, and Prisma, Firebase, and Netlify all ship it as the local engine behind their database products.
Where it wins: test suites (a fresh, real Postgres per test, no containers in CI), instant onboarding, browser playgrounds, coding-agent sandboxes, any environment where Docker isn't available.
Where it doesn't: it's single-connection and single-user. No replication, not a production database, and tools that expect a TCP connection string need an adapter. It's a library, and everything that implies cuts both ways.
2. Native install (~50 to 300 MB)
Homebrew, apt, or Postgres.app. The old way, and still the fastest raw Postgres you can run, since there's no VM and no container layer.
Where it wins: one long-lived database you use daily, maximum performance, lowest conceptual overhead.
Where it doesn't: one global version for the whole machine. Two projects on different Postgres majors means version-manager gymnastics. It pollutes the host, upgrades are a small ritual, and "install Postgres 16 locally" is the onboarding step that fails differently on every teammate's laptop.
3. Docker (~400 MB image, plus the runtime)
The default answer of the last decade. docker compose up, one isolated Postgres per project, any version you like, throw it away when you're done.
Where it wins: version isolation per project, parity with a containerized production setup, and the whole team runs the identical environment from one committed file.
Where it doesn't: the real cost isn't the image, it's the runtime. Docker Desktop idles at gigabytes of RAM on Mac and Windows, licensing applies at larger companies, CI needs service containers with health checks and port juggling, and there are environments (locked-down laptops, cloud IDEs, agent sandboxes) where it simply can't run. You're running a VM to get a process.
4. Cloud dev branches (0 MB local, network required)
Neon-style branching, or your platform's preview databases: every PR gets its own copy-on-write database in the cloud, nothing on your machine.
Where it wins: production-identical infrastructure, instant branches with real data shapes, zero local footprint, great for review apps.
Where it doesn't: no network, no database. Every query pays a round trip, tests share remote state unless you're disciplined about branching, and it's the only option on this list with a monthly bill. It complements a local option more than it replaces one.
The cheat sheet
| Use case | Pick |
|---|---|
| Unit and integration tests | PGlite (fresh DB per test) |
| CI pipelines | PGlite, or Docker service containers if you need TCP |
| One daily-driver database | Native install |
| Multiple projects, multiple versions | Docker |
| Matching containerized prod | Docker |
| PR preview environments | Cloud branches |
| Demos, workshops, browser sandboxes | PGlite |
Most teams end up with two: something heavy for prod parity, something feather-weight for the inner loop. That split is the point. The 200 iterations a day don't need the faithful production copy; the last check before deploy does.
The Supabase-shaped footnote
One case the table above doesn't cover: your app doesn't talk to bare Postgres. If you build on Supabase, your client calls the platform's REST, Auth, Storage, and Realtime APIs, so a lightweight Postgres alone doesn't give you a lightweight backend. The official local stack solves it with Docker, about a dozen containers of it.
That's the gap tinbase fills: an open-source (MIT) Supabase-compatible backend that runs the whole API surface on PGlite, in one process, npx tinbase start, up in about two seconds, with the official supabase-js SDK working unchanged. Same idea as row 1 of the table, extended to the entire platform. It's alpha and aimed at local dev and tests, not production, which is exactly the inner-loop slot in the two-tier split above.
Whatever your stack, weigh the setup against how you actually spend your day. The database you iterate against should cost you nothing to start, nothing to reset, and nothing to explain in the onboarding doc.
Top comments (0)