name: supabase-cloud-migrations-vps-deploy
description: Deploy Supabase Cloud migrations from VPS when supabase db push fails on "already exists" and CLI connects to local Docker instead of remote
category: supabase
Supabase Cloud Migrations — VPS Deploy Workaround
Context
When deploying a Supabase Cloud project to VPS using supabase db push, two critical non-obvious behaviors must be understood:
1. supabase db push connects to LOCAL Docker Postgres, NOT the remote Cloud database — even after supabase link succeeds.
2. There is NO --force or --skip-existing flag in supabase db push.
3. Service role key fails (401/403) for management API — Cloudflare blocks direct management calls.
4. If a migration statement fails, ALL remaining migrations are skipped — no partial progress.
Workflow
Step 1 — Link the project
`bash
supabase login # with Management API token (sbp_...)
supabase link --project-ref
`
Step 2 — Attempt migration push
`bash
supabase db push
`
- - If it fails on
CREATE TYPE ... already exists→ migration 1 partially applied - - The CLI connects to LOCAL Docker, NOT the Cloud project
- - Use
--dry-runto preview what would be pushed - - CLI db push → LOCAL Docker:
supabase db pushalways connects to local Docker Postgres, not the Cloud project. Thelinkcommand only sets the project ref forprojects listand management API calls. - - Migration atomicity: A single failing statement in a migration file stops the entire
supabase db push. UseDO $$ BEGIN ... EXCEPTION WHEN duplicate_object THEN null; END $$wrapper pattern. - - Existing vs new schema: If the Cloud database already has tables from a previous/broken deploy, their schema may differ from migration files. Check actual schema via REST API before running migrations.
- - Service role key 401: Management API (api.supabase.com) returns 401 with service role key due to Cloudflare. Use CLI
supabase logininstead. - - pg_catalog / information_schema: Not accessible via PostgREST REST API — use SQL Editor instead.
Step 3 — Handle "already exists" failures
The cleanest workaround: modify the first migration to use safe syntax
On the VPS, edit the first migration file. Replace each CREATE TYPE with a DO block:
`sql
-- BEFORE:
CREATE TYPE public.app_role AS ENUM ('corretor', 'adm');
-- AFTER:
DO $$ BEGIN
CREATE TYPE public.app_role AS ENUM ('corretor', 'adm');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
`
For tables, use CREATE TABLE IF NOT EXISTS or wrap in similar DO blocks.
Step 4 — Alternative: SQL Editor direct approach
If CLI approach fails completely:
1. Open https://supabase.com/dashboard/project//sql
2. Log in with the project owner's email (e.g., sales.planoss@gmail.com)
3. Use browser SQL Editor to paste consolidated SQL
Step 5 — Verify remote tables exist
Use REST API (anon key works for SELECT):
`python
import urllib.request, json
ANON_KEY = "your_anon_key"
PROJECT_REF = "your_project_ref"
for table in ['clients', 'documents', 'profiles']:
url = f"https://{PROJECT_REF}.supabase.co/rest/v1/{table}?select=id&limit=1"
req = urllib.request.Request(url, headers={"apikey": ANON_KEY})
try:
with urllib.request.urlopen(req) as r:
print(f"OK {table}: {json.loads(r.read())}")
except urllib.error.HTTPError as e:
print(f"FAIL {table}: {e.code}")
`
Key Pitfalls
Verification
After migrations, verify all expected tables exist via REST API:
`bash
curl -s "https://.supabase.co/rest/v1/ -H "apikey: -H "Authorization: Bearer ?select=id&limit=1" \
`