Self-Hosting

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:

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:

1
supabase db dump --db-url "[CONNECTION_STRING]" -f roles.sql --role-only
1
supabase db dump --db-url "[CONNECTION_STRING]" -f schema.sql
1
supabase db dump --db-url "[CONNECTION_STRING]" -f data.sql --use-copy --data-only

This produces SQL files that are compatible across Postgres versions.

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:

1
postgres://postgres.your-tenant-id:[POSTGRES_PASSWORD]@[your-domain]:5432/postgres

Where [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:

1
psql \
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:

1
psql "postgres://postgres.your-tenant-id:[POSTGRES_PASSWORD]@[your-domain]:5432/postgres"
1
-- Check your tables are present
2
\dt public.*
3
4
-- Verify row counts on key tables
5
SELECT count(*) FROM auth.users;
6
7
-- Check extensions
8
SELECT * 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 setupHow to configure
JWT secrets and API keysGenerate new ones and update .env
Auth provider settings (OAuth, Apple, etc.)Configure GOTRUE_EXTERNAL_* variables in .env
Edge functionsManually copy to your self-hosted instance
Storage objectsTransfer separately (not covered in this guide)
SMTP / email settingsConfigure SMTP_* variables in .env
Custom domains and DNSPoint 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 .env file. Set the relevant GOTRUE_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 15
  • COPY statements for tables that don't exist on self-hosted (e.g., auth.oauth_clients, storage.buckets_vectors, storage.vector_indexes)
  • COPY statements with columns added in newer Auth versions (e.g., auth.flow_state with oauth_client_state_id, linking_target_id)

Workaround: Edit data.sql before restoring:

1
# Comment out PG17-only transaction_timeout
2
sed -i 's/^SET transaction_timeout/-- &/' data.sql

For 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:

1
select * 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:

1
ALTER ROLE your_custom_role WITH PASSWORD 'new-password';

Additional resources#