2026-06-11
Migrating off Supabase to self-hosted Postgres on Coolify
Moving a production app off Supabase and onto self-hosted Postgres on Coolify to kill the monthly Pro charge. The restore did not go cleanly on the first try.
I was shutting down a Supabase org to stop paying for the Pro plan, consolidating everything onto a VPS I already pay for and run through Coolify. Of the projects on that org, one was a genuine live migration: a Postgres database backing a production app, with real auth users and RLS policies, that needed to end up on self-hosted Postgres with zero data loss.
Getting the dump out
Supabase's connection pooler (the aws-1 pooler) does not let a plain pg_dump through directly in every setup. What worked was creating a temporary BYPASSRLS role and dumping through that, over the pooler connection, which gets you everything including the auth schema and password hashes, not just the public tables.
The restore problem: Postgres 17 dump into a Postgres 15 stack
The self-hosted target stack runs Postgres 15. Supabase's managed Postgres was on 17 by the time I dumped it, and a pg_dump taken with the newer client/server tooling embeds syntax the older server's restore path does not understand. Restoring the dump as-is failed.
The fix is to strip three classes of line from the dump before restoring it into the Postgres 15 target:
- \restrict lines
- transaction_timeout settings
- CREATE SCHEMA public statements (the target already has a public schema; re-creating it is what breaks the restore)
Strip those and the restore proceeds cleanly against the older server.
What I verified before calling it done
Before treating a migration as real, I checked auth (real logins working against the restored auth schema), data (row counts and spot checks), RLS (policies still enforcing correctly post-restore), and storage. Cutover itself, and the org downgrade from Pro to Free, are separate gated steps I held until every one of those checked out on the restored copy, not just "the restore command exited 0".
Worth calling out directly since it will not be true for every project on an org: one of my other apps that looked like a Supabase migration candidate turned out to already be running on self-hosted Postgres with iron-session and zero @supabase imports in the codebase, with the Supabase project sitting there as an orphan to just back up and delete. If you are working through a multi-project org shutdown, check each project's actual code for @supabase imports before assuming it needs a live migration; some of them may already be off it and just still exist as a forgotten project record.
Versions
- Source: Supabase managed Postgres 17 (aws-1 pooler)
- Target: self-hosted Postgres 15 on Coolify
Have Emsden Studio fix it for you, from A$149