Supabase 跨区域迁移
核心认知(先确认,别走弯路)
Supabase 不支持原地改区域——项目在基础设施层绑定区域。官方原文: "Each Supabase project is provisioned on hardware in the chosen region, so it is bound to a region at the infrastructure level."
所以迁移永远是:目标区新建项目 → 迁 schema → 迁数据 → 改指向。
为什么不需要数据库密码
传统方案用 pg_dump,需要数据库密码。但本项目验证过一条更好的路:
| 环节 | 传统做法 | 本方案 |
|---|---|---|
| schema | pg_dump(需密码 + Docker) | supabase db push(用 migrations,密码自建) |
| 数据 | pg_dump / pg_restore | PostgREST + service_role(绕过 RLS) |
| auth 用户 | 导出 users 表 | Admin API 指定 UUID 重建 |
新项目的 db_pass 由我们在创建时指定,所以密码始终掌握在自己手里。
流程
0. 前置检查
supabase projects list # 确认源项目 ref / 当前区域 / 登录态
supabase --version # CLI ≥ 2.x
记下 organization_id(第二列 ORG ID)。
1. 在目标区域创建项目
TOKEN=$(cat ~/.supabase/access-token)
curl -X POST "https://api.supabase.com/v1/projects" \
-H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"organization_id":"<org>","name":"HIM-Tokyo","region":"ap-northeast-1","db_pass":"<自设>","plan":"free"}'
⚠️ 密码不要以 ! 结尾——shell 会做历史扩展吃掉它。后续连接一律让 node
直接读密码文件 + encodeURIComponent,别用 shell 变量传。
常用区域:ap-northeast-1(东京)、ap-southeast-1(新加坡)、ca-central-1。
2. 推 schema(走 pooler,别走直连)
supabase db push --yes --db-url \
"postgresql://postgres.<NEW_REF>:<PW>@aws-0-<REGION>.pooler.supabase.com:5432/postgres"
- ⚠️ 必须加
--yes,否则卡在交互确认(表现为空日志 + 进程 SIGKILL 137) - ⚠️ 新项目初期
db.<ref>.supabase.co直连 DNS 未生效(ENOTFOUND),用 pooler - pooler 5432 = session 模式(DDL 用这个);6543 = transaction 模式
3. 建 storage bucket
先查源项目有哪些 bucket:
curl -s "$SRC_URL/storage/v1/bucket" -H "Authorization: Bearer $SRC_SERVICE_ROLE_KEY"
再在目标项目同参数创建(id / public / file_size_limit / allowed_mime_types)。
4. 迁 auth 用户(必须先做)
public.users.user_id 外键引用 auth.users.id,不先建 auth 用户会导致后续全表级联失败。
Admin API 支持指定 UUID 建用户(已验证),这是保住数据链的关键:
POST /auth/v1/admin/users
{ "id":"<原 UUID>", "email":"...", "email_confirm":true, "password":"<随机>", "user_metadata":{...} }
密码哈希无法导出,建完用 generate_link 生成重置链接:
POST /auth/v1/admin/generate_link { "type":"recovery", "email":"..." }
5. 迁数据
用 scripts/migrate_supabase_region.mjs(本项目内,支持 --dry 预演):
export SRC_URL=... SRC_KEY=<源 service_role>
export TGT_URL=... TGT_KEY=<目标 service_role>
node scripts/migrate_supabase_region.mjs --dry # 先预演
node scripts/migrate_supabase_region.mjs # 再实跑
按外键顺序:categories → users → households → items → inventory_events → ...
6. 对齐分类 UUID(高频坑)
migration 预置的种子分类与源库同名不同 UUID,items.category_id 引用源 UUID
会撞外键。解法:清空目标库 categories,用源库数据重建以对齐 UUID。
(此时目标库无引用,安全。注意主键是 category_id 不是 id。)
7. 同步 auth 配置
curl -X PATCH "https://api.supabase.com/v1/projects/<NEW_REF>/config/auth" \
-d '{"site_url":"...","uri_allow_list":"..."}'
⚠️ uri_allow_list 是逗号分隔字符串,传数组只会存第一个。
8. 切环境变量 + 同步远端
改 .env.local 前先备份。远端(Vercel / EdgeOne)也要同步,否则线上仍连旧库。
验证
逐表对比源/目标行数,用 Prefer: count=exact 读 content-range 的总数。
迁移后须知
- 所有用户会话失效(新 JWT secret),密码需重设
- storage 文件不随数据库迁移,需另行
rclone/aws s3 sync - Edge Functions、Cron、SMTP 需手动重建
- 免费版注意活跃项目数上限(INACTIVE 项目不占额度)