Restore a Platform Project to Self-Hosted
Restore your database from the Supabase platform to a self-hosted instance.
This guide walks you through restoring your database from a Supabase platform project to a self-hosted Docker instance. Storage objects transfer or redeploying edge functions is not covered here.
Before you begin
You need:
- A new self-hosted Supabase instance (Docker setup guide)
- Supabase CLI installed (or use
npx supabase) - Docker Desktop installed (required by the CLI)
psqlinstalled (official installation guide)- Your Supabase database passwords (for platform and self-hosted)
Step 1: Get your platform connection string
On your managed Supabase project dashboard, click Connect and copy the connection string (use the session pooler or direct connection).
Step 2: Back up your platform database
Export roles, schema, and data as three separate SQL files:
1supabase db dump --db-url "[CONNECTION_STRING]" -f roles.sql --role-only1supabase db dump --db-url "[CONNECTION_STRING]" -f schema.sql1supabase db dump --db-url "[CONNECTION_STRING]" -f data.sql --use-copy --data-onlyThis produces SQL files that are compatible across Postgres versions.
Using supabase db dump executes pg_dump under the hood but applies Supabase-specific filtering - it excludes internal schemas, strips reserved roles, and adds idempotent IF NOT EXISTS clauses. Using raw pg_dump directly will include Supabase internals and cause permission errors during restore. CLI requires Docker because it runs pg_dump inside a container from the Supabase Postgres image rather than requiring a local Postgres installation.
Step 3: Prepare your self-hosted instance
Before restoring, check the following on your self-hosted instance:
- Extensions: Enable any non-default extensions your Supabase project uses. You can check which extensions are active by querying
select * from pg_extension;on your managed database (or check Database Extensions in Dashboard).
Step 4: Restore to your self-hosted database
Connect to your self-hosted Postgres and restore the dump files. The default connection string for self-hosted Supabase is:
1postgres://postgres.your-tenant-id:[POSTGRES_PASSWORD]@[your-domain]:5432/postgresWhere [POSTGRES_PASSWORD] is the value of POSTGRES_PASSWORD in your self-hosted .env file.
Use your domain name, your server IP, or localhost for [your-domain] depending on whether you are running self-hosted Supabase on a VPS, or locally.
Run psql to restore:
1psql \2 --single-transaction \3 --variable ON_ERROR_STOP=1 \4 --file roles.sql \5 --file schema.sql \6 --command 'SET session_replication_role = replica' \7 --file data.sql \8 --dbname "postgres://postgres.your-tenant-id:[POSTGRES_PASSWORD]@[your-domain]:5432/postgres"Setting session_replication_role to replica disables triggers during the data import, preventing issues like double-encryption of columns.
Step 5: Verify the restore
Connect to your self-hosted database and run a few checks:
1psql "postgres://postgres.your-tenant-id:[POSTGRES_PASSWORD]@[your-domain]:5432/postgres"1-- Check your tables are present2\dt public.*34-- Verify row counts on key tables5SELECT count(*) FROM auth.users;67-- Check extensions8SELECT * FROM pg_extension;What's included in the restore and what's not
The database dump includes your schema, data, roles, RLS policies, database functions, triggers, and auth.users. However, several things require separate configuration on your self-hosted instance:
| Requires manual setup | How to configure |
|---|---|
| JWT secrets and API keys | Generate new ones and update .env |
| Auth provider settings (OAuth, Apple, etc.) | Configure GOTRUE_EXTERNAL_* variables in .env |
| Edge functions | Manually copy to your self-hosted instance |
| Storage objects | Transfer separately (not covered in this guide) |
| SMTP / email settings | Configure SMTP_* variables in .env |
| Custom domains and DNS | Point your DNS to the self-hosted server |
Auth considerations
Your auth.users table and related data are included in the database dump, so user accounts are preserved. However:
- JWT secrets differ between your platform and self-hosted instances. Existing tokens issued by the platform project will not be valid. Users will need to re-authenticate.
- Social auth providers (Apple, Google, GitHub, etc.) need to be configured in your self-hosted
.envfile. Set the relevantGOTRUE_EXTERNAL_*variables. See the Auth repository README for all available options. - Redirect URLs in your OAuth provider consoles (Apple Developer, Google Cloud Console, etc.) must be updated to point to your self-hosted hostname instead of
*.supabase.co.
Postgres version compatibility
Managed Supabase may run a newer Postgres version (Postgres 17) than the self-hosted Docker image (currently Postgres 15). The supabase db dump command produces plain SQL files that work across major Postgres versions.
Keep in mind:
- The data dump may include Postgres 17-only settings or reference tables/columns from newer Auth and Storage versions that don't exist on self-hosted yet. See Version mismatches in the troubleshooting section.
- Run the restore on a test self-hosted instance first to identify any incompatibilities.
- Check that all extensions you use are available on the self-hosted Postgres version.
Troubleshooting
Version mismatches between platform and self-hosted
The platform may run a newer Postgres version (17 vs 15) and newer Auth service versions than self-hosted. The data dump can contain settings, tables, or columns that don't exist on your new self-hosted instance.
Common issues in data.sql:
SET transaction_timeout = 0- a Postgres 17-only setting that fails on Postgres 15COPYstatements for tables that don't exist on self-hosted (e.g.,auth.oauth_clients,storage.buckets_vectors,storage.vector_indexes)COPYstatements with columns added in newer Auth versions (e.g.,auth.flow_statewithoauth_client_state_id,linking_target_id)
Workaround: Edit data.sql before restoring:
1# Comment out PG17-only transaction_timeout2sed -i 's/^SET transaction_timeout/-- &/' data.sqlFor missing tables or column mismatches, comment out the relevant COPY ... FROM stdin; line and its corresponding \. terminator. Run the restore without --single-transaction first to identify all failures, then fix them and run the final restore with --single-transaction.
Keeping your self-hosted configuration up to date will minimize these gaps.
Extension not available
If the restore fails because an extension isn't available, check whether it's supported on your self-hosted Postgres version. You can list available extensions with:
1select * from pg_available_extensions;Connection refused
Make sure your self-hosted Postgres port is accessible. In the default self-hosted Supabase setup, the user is postgres.your-tenant-id with Supavisor on port 5432.
Legacy Studio configuration
Studio in self-hosted Supabase historically used supabase_admin role (superuser) instead of postgres. Objects created via Studio UI were owned by supabase_admin. Check your docker-compose.yml configuration to see if POSTGRES_USER_READ_WRITE is set to postgres.
Custom roles missing passwords
If you created custom database roles with the LOGIN attribute on your platform project, their passwords are not included in the dump. Set them manually after restore:
1ALTER ROLE your_custom_role WITH PASSWORD 'new-password';