all posts

The Best Heroku Postgres Alternatives in 2026

Ajay Kumar··10 min read

Heroku Postgres deserves more credit than it usually gets. It made managed Postgres normal — automatic backups, a single connection string, a fork command, follower databases — years before that was table stakes. If you've never had to think about WAL archiving, this is partly why.

The reasons people leave in 2026 are consistent, and none of them are about reliability. It's the price per gigabyte at anything above hobby scale, the connection limits on lower tiers landing badly with modern serverless traffic, and a general sense that the platform has stopped moving while the rest of the Postgres world moved a lot.

Disclosure: I'm the founder of PandaStack, one of the options below. Specific figures are quoted only for our own platform; every other provider is described qualitatively from its own documentation. Managed database pricing changes often — verify before you commit.

Work out what you'd actually be giving up

Before comparing anything, list which Heroku Postgres features you're genuinely using. Most teams use three of them and worry about ten.

  • Automatic backups with point-in-time recovery. Every option below has an equivalent; check the retention window, not just the presence of a checkbox.
  • pg:fork — a full copy of the database for testing. This is the one people miss most, and the modern equivalent is branching, which is faster and cheaper.
  • Follower databases for read scaling. Genuinely useful, and not universally offered — verify it exists if you rely on it.
  • The release phase running migrations before a new version takes traffic. That's a dyno feature, not a database one, and you'll need to reproduce it wherever you host the app.
  • DATABASE_URL just being there. Trivially replaced, but it's the thing that makes Heroku feel effortless, and you should expect to configure it explicitly elsewhere.

The options

Neon

Serverless Postgres with storage and compute separated, and branching as the headline feature. Creating a branch is near-instant and costs almost nothing, which changes how teams test — every pull request can have a real database with real data.

Shines when: your workload is spiky or development-heavy. Compute suspends when idle, branching is genuinely excellent, and the free tier is generous enough to be useful.

Caveat: the architecture puts storage across a network from compute, so a cold branch reads pages remotely until its cache warms. For most applications that's invisible; for latency-sensitive read paths it's worth benchmarking rather than assuming.

Supabase

Postgres plus a platform — auth, storage, realtime subscriptions, auto-generated REST and GraphQL APIs, and row-level security wired through all of it.

Shines when: you want more than a database. If you'd otherwise be building auth and file storage yourself, the bundle is a genuine accelerator, and it's still just Postgres underneath.

Caveat: you're adopting a platform, not a database. If all you wanted was somewhere to put your tables, there's a lot of surface area you're not using, and the RLS-centric model is opinionated in ways that take some getting used to.

Amazon RDS and Aurora

The boring, unimpeachable choice. Every feature, every instance size, every compliance certification, and it sits inside your VPC next to everything else.

Shines when: you're already on AWS, or your requirements are written in terms of certifications and controls. Nobody was ever fired for choosing RDS.

Caveat: it's an instance, so you pay for it around the clock at the size you provisioned, and provisioning is your job. There's no branching, no scale-to-zero on standard RDS, and the developer experience is decidedly infrastructure-flavoured.

Crunchy Bridge

Managed Postgres from people deeply embedded in the Postgres community. Unopinionated, standards-focused, with strong extension support and no proprietary layer between you and the database.

Shines when: you want Postgres, properly, with expertise behind it and no platform ambitions attached.

Caveat: fewer of the modern developer-experience features — branching, scale-to-zero — than the newer entrants. That's a deliberate positioning, not an oversight.

PandaStack

Ours, and the architecture is the differentiator: each database is stock PostgreSQL 16 in its own Firecracker microVM with a dedicated durable volume, rather than a tenant inside shared machinery. Its memory is never overcommitted, and it has its own kernel.

Because the unit is a whole machine, branching works by forking the machine — disk and memory together — so a branch arrives with the parent's buffer cache already warm. The first query on a branch hits a hot cache instead of re-reading from remote storage, which is the cold-start tax that separated-storage designs pay. Each branch gets its own connection string, its own credentials, and its own backup stream, and diverges copy-on-write from the parent.

The rest is the expected list, verified rather than implied: daily base backups plus continuous WAL archiving, so point-in-time recovery lands on any second in the window; restore in place keeping the same connection string, or clone into a new database to inspect old data safely; PgBouncer in transaction mode on a pooled URL accepting up to 500 client connections; an HTTP query broker for edge runtimes that can't open a TCP socket; and an IP allow list. pgvector, pg_trgm, and pgcrypto are pre-installed.

Pricing is the same card as every other workload: $0.054 per active vCPU-hour and $0.0162 per working-set GiB-hour while awake, plus storage at $0.15 per GiB-month. A database that auto-suspends bills storage only. An auto-suspended database bills storage only, so a development database that sleeps most of the day costs a few cents.

Caveat: it's a younger platform than RDS with fewer regions, and read replicas aren't part of the story today. If your read path needs followers, that's a real gap.

Postgres on a VM you manage

Cheapest per gigabyte by a wide margin, and a perfectly reasonable choice for a side project or a team with genuine operational depth.

Caveat: you now own backups, and — more importantly — you own testing that they restore. The failure mode here isn't a slow database; it's discovering during an incident that the backup you've been taking for eight months has been failing silently for seven of them.

The connection-limit trap, since it's often the real reason

A large share of Heroku Postgres pain isn't the database at all — it's that the plan's connection limit and a serverless application are a bad match. Postgres forks a process per connection; a serverless runtime opens one per concurrent invocation. Twenty concurrent requests, twenty connections, and a plan capped at 20 means the twenty-first request fails.

If that's your symptom, a pooler fixes it wherever you are, and it may fix it without a migration. Point application traffic at a transaction-mode pooled connection string and keep the direct one for migrations:

# Application traffic — pooled, hundreds of clients onto a few backends
DATABASE_URL=postgres://user:pw@host:6432/db?sslmode=require

# Migrations, psql, LISTEN/NOTIFY — direct
DIRECT_URL=postgres://user:pw@host:5432/db?sslmode=require

It's worth confirming this is your problem before choosing a database on the strength of it, because the fix is a connection string rather than a migration.

Migrating without a long outage

For most applications, a dump-and-restore with a maintenance window is the right call — it's simple, it's easy to verify, and the window is usually minutes. Rehearse it once against a scratch database so you know the real number before you announce one.

# 1. Rehearse. Time this; it's your maintenance window.
pg_dump --format=custom --no-owner --no-acl "$HEROKU_URL" > db.dump
pg_restore --no-owner --no-acl --dbname="$NEW_URL" db.dump

# 2. Verify before you cut over — row counts per table, both sides
psql "$HEROKU_URL" -c "select relname, n_live_tup from pg_stat_user_tables order by relname"
psql "$NEW_URL"    -c "select relname, n_live_tup from pg_stat_user_tables order by relname"

# 3. Extensions travel separately. Check what you were using.
psql "$HEROKU_URL" -c "select extname, extversion from pg_extension"

On the day: put the app in maintenance mode, take a final dump, restore it, switch DATABASE_URL, and bring the app back. Keep the Heroku database running and untouched for a week — it's your rollback, and it's cheap insurance.

If minutes of downtime genuinely isn't acceptable, logical replication is the answer: replicate into the new database, let it catch up, and cut over when the lag is near zero. It's more moving parts and it's the right tool when the window matters.

The two things that break a migration after the switch: extensions that existed on the old database and not the new one, and sequences that weren't reset — leaving your next INSERT colliding with an existing id. Check both before you announce success, not after the first error report.

Which one

  • You want branching and a generous free tier, and your traffic is spiky → Neon.
  • You want auth, storage, and realtime bundled with the database → Supabase.
  • You're on AWS, or compliance dictates the answer → RDS or Aurora.
  • You want Postgres with deep expertise and no platform layer → Crunchy Bridge.
  • You want a dedicated machine per database, warm branching, and to stop paying for idle → PandaStack.
  • It's a side project and you enjoy this sort of thing → a VM, with a genuinely tested restore.

The honest summary: Heroku Postgres is still a fine database, and if your bill is small and your traffic is steady there's no urgent reason to move. Everyone else is leaving for one of two things — the cost of idle capacity, or branching — and those two features are what should decide where you land.

Frequently asked questions

Why is Heroku Postgres considered expensive?

The pricing is tied to plan tiers rather than to what you use, and the steps between tiers are large. Crossing a row-count or storage boundary can double your bill for a marginal increase in data, and there's no concept of paying less when the database is idle — a staging database bills the same at 3am as production does at peak. Newer providers price on consumption, on smaller increments, or with an idle state that costs only storage, so the same workload often lands at a fraction of the cost. The gap is widest exactly where most teams sit: several small-to-medium databases that are mostly quiet.

What replaces Heroku's pg:fork?

Branching, and it's a significant upgrade. pg:fork provisions a new database and copies the data, so a large fork takes a while and costs a full second copy. Branching on modern platforms is copy-on-write: the branch shares the parent's data until it writes, then diverges page by page, which makes creation near-instant and storage close to free until the branch actually changes something. Neon popularised it; PandaStack forks the whole machine including memory, so the branch also inherits the parent's warm buffer cache and its first query doesn't pay a cold-read penalty.

How do I migrate off Heroku Postgres without downtime?

For most applications, don't try — a dump-and-restore with a short maintenance window is simpler, easier to verify, and usually a matter of minutes. Rehearse it against a scratch database first so the window you announce is a measured number rather than a guess. If genuine zero downtime is required, set up logical replication from the Heroku database into the new one, let it catch up, verify row counts, then cut the application over when replication lag is near zero. Either way, keep the old database running untouched for a week afterwards as a rollback path.

Will my Postgres extensions come with me?

Not automatically, and this is the most common post-migration surprise. A dump records that an extension was in use but doesn't carry the extension itself, so the restore fails or the application breaks at the first query needing it. Run select extname, extversion from pg_extension on the source before you migrate and check each one against the destination's supported list. Common ones like pgcrypto, pg_trgm, uuid-ossp, and pgvector are widely available; anything less common is worth confirming explicitly rather than assuming.

Is 'too many connections' a reason to change database provider?

Usually not by itself — it's a pooling problem, and a pooler fixes it wherever your database lives. Postgres forks a process per connection while serverless runtimes open one per concurrent invocation, so the two models collide at surprisingly low traffic. Routing application traffic through a transaction-mode pooler lets hundreds of clients share a handful of real backends, and it's a connection-string change rather than a migration. Change provider if the pricing, the branching story, or the idle cost is the problem; fix connections with a pooler first and see whether the rest of the complaint survives.

Keep reading

Run code in a microVM in one API call.

49ms p50 cold start. Fork, snapshot, and scale to zero.

Start free
Written by Ajay Kumar, Founder, PandaStack.