Covers: - Connection test (direct vs pooler, IPv4 vs IPv6) - pg_dump commands (schema-only and data-only) - Restore steps to local DB - Verification queries - Schema-per-brand restructure (future work)
5.6 KiB
Supabase Dump & Restore Guide
This guide is the runbook for when your brother removes the Supabase spend cap and we can finally connect to the live Supabase project to pull the real schema + data.
Prerequisites
- Brother has removed the Supabase spend cap (project status: ACTIVE_HEALTHY)
- Supabase project ref:
wnzkhezyhnfzhkhiflrp(from CLAUDE.md) - Supabase DB password:
YLKzP9jz2yqop7jr(user-provided) - Local Postgres running on
127.0.0.1:5432(user:routecommerce, password:routecommerce_dev_password, db:route_commerce)
Connection Test
The Supabase hostname has NO IPv4 address from this dev box. You must use the Supabase Supavisor pooler (port 6543) which resolves over IPv4. From your home network, you can also use the direct hostname on port 5432.
# Direct hostname (home network only — dev box has no IPv6)
psql "postgres://postgres.wnzkhezyhnfzhkhiflrp:YLKzP9jz2yqop7jr@db.wnzkhezyhnfzhkhiflrp.supabase.co:5432/postgres"
# Pooler (works from dev box — IPv4 only)
psql "postgres://postgres.wnzkhezyhnfzhkhiflrp:YLKzP9jz2yqop7jr@aws-0-us-east-1.pooler.supabase.com:6543/postgres"
If neither works after the cap is removed, check:
getent hosts db.wnzkhezyhnfzhkhiflrp.supabase.co— should return IPv4- Supabase dashboard → Settings → API → "Direct connection" string
Capture Schema (no data)
# From your home network, with the password set:
PGPASSWORD="YLKzP9jz2yqop7jr" pg_dump \
--host=db.wnzkhezyhnfzhkhiflrp.supabase.co \
--port=5432 \
--username=postgres \
--dbname=postgres \
--schema-only \
--no-owner \
--no-privileges \
--no-acl \
--file=supabase/captured_schema.sql
This produces a SQL file with all CREATE TABLE, CREATE FUNCTION, CREATE INDEX, etc. statements. May be 50-200 MB depending on the function count. Compress it before committing:
gzip supabase/captured_schema.sql # → captured_schema.sql.gz
Capture Data (no schema)
PGPASSWORD="YLKzP9jz2yqop7jr" pg_dump \
--host=db.wnzkhezyhnfzhkhiflrp.supabase.co \
--port=5432 \
--username=postgres \
--dbname=postgres \
--data-only \
--no-owner \
--no-privileges \
--no-acl \
--disable-triggers \
--file=supabase/captured_data.sql
Use --disable-triggers so the dump skips triggers that might fire
on INSERT and slow things down. Compress:
gzip supabase/captured_data.sql
Restore to Local DB
cd /path/to/route-commerce
export PGPASSWORD=routecommerce_dev_password
# 1. Wipe the synthesized schema
psql -U routecommerce -h 127.0.0.1 -d route_commerce \
-c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
# 2. Apply the real schema
zcat supabase/captured_schema.sql.gz | \
psql -U routecommerce -h 127.0.0.1 -d route_commerce \
-v ON_ERROR_STOP=1 2>&1 | tee /tmp/schema_restore.log
# 3. Apply the data
zcat supabase/captured_data.sql.gz | \
psql -U routecommerce -h 127.0.0.1 -d route_commerce \
-v ON_ERROR_STOP=1 2>&1 | tee /tmp/data_restore.log
Verify
# Should show ~70+ tables (not just the 70 from the synthesized schema)
psql -U routecommerce -h 127.0.0.1 -d route_commerce -c "\dt"
# Should show actual data, not 0 rows everywhere
psql -U routecommerce -h 127.0.0.1 -d route_commerce -c "SELECT count(*) FROM brands;"
psql -U routecommerce -h 127.0.0.1 -d route_commerce -c "SELECT count(*) FROM products;"
psql -U routecommerce -h 127.0.0.1 -d route_commerce -c "SELECT count(*) FROM orders;"
Clean Up
# Once verified, drop the synthesized schema (no longer needed)
rm supabase/synthesized/000_base_schema.sql
git add -A
git commit -m "remove synthesized schema (real dump now in place)"
Schema-Per-Brand Restructure (FUTURE, not in this dump)
The user wants each brand in its own Postgres schema:
brand_tuxedo— products, orders, stops, etc. for Tuxedobrand_indian_river_direct— products, orders, stops, etc. for IRD
This is a major refactor and is NOT part of the dump-and-restore. Plan for it as a follow-up phase once the data is loaded into shared tables and the app is verified working.
The refactor will involve:
- Creating schemas
brand_tuxedo,brand_indian_river_direct - Moving brand-specific tables into the appropriate schema
- Updating all RPC SECURITY DEFINER functions to set
search_pathbased on caller brand - Updating server actions to set
search_pathper request - Updating PostgREST config to expose schemas
- Testing brand isolation thoroughly
Troubleshooting
"password authentication failed"
Password has been rotated. Check Supabase dashboard → Settings →
Database → Reset password. The user-provided password
YLKzP9jz2yqop7jr may need to be reset.
"could not translate host name"
Dev box has no IPv6. Use the pooler hostname
aws-0-us-east-1.pooler.supabase.com or run from a machine with IPv6.
"permission denied for schema auth"
The dump includes the auth schema which is Supabase-managed.
Filter it out with: --exclude-schema=auth --exclude-schema=storage --exclude-schema=realtime --exclude-schema=supabase_functions.
"relation already exists"
You forgot step 1 (DROP SCHEMA public CASCADE). The data restore
runs INSERT and may not recreate tables — only the schema dump
does that.
"out of memory" during data restore
The data file is huge. Use --single-transaction to keep
Postgres from spilling to disk:
zcat supabase/captured_data.sql.gz | \
psql -U routecommerce -h 127.0.0.1 -d route_commerce \
--single-transaction \
-v ON_ERROR_STOP=0 2>&1 | tee /tmp/data_restore.log
Data dump is too big to commit
Don't commit it. The data file is in .gitignore and gets
re-loaded on first run via a one-shot script.