| audience | both |
|---|---|
| applies-to | post-scaffold |
本文件說明如何部署與維護 Self-hosted Supabase,適用於需要完全控制基礎設施的情境。
| 情境 | 推薦方案 |
|---|---|
| 快速原型、小型專案 | Supabase Cloud |
| 資料隱私要求高 | Self-hosted |
| 需要客製化設定 | Self-hosted |
| 無法存取外部網路 | Self-hosted |
| 成本控制需求 | Self-hosted |
[Client] → [Cloudflare Tunnel / Nginx] → [Kong Gateway :8000]
↓
[PostgreSQL] ← [Auth/Storage/Realtime]
↓
[Studio :3000]
| 項目 | Cloud | Self-hosted |
|---|---|---|
| URL | https://<ref>.supabase.co |
自訂 domain |
| Migration 部署 | supabase db push |
docker exec 執行 SQL |
| Studio | supabase.com/dashboard |
自架 Studio |
| 資料庫存取 | API Gateway 僅限 | 可直連或 Tunnel |
| CI/CD 整合 | 自動推送 | 手動執行或腳本觸發 |
| MCP 端點 | mcp.supabase.com |
自架 Kong MCP 路由 |
# 建立部署目錄
mkdir -p /opt/supabase
cd /opt/supabase
# 下載官方 docker-compose
git clone --depth 1 https://github.com/supabase/supabase.git
cp -r supabase/docker/* .
rm -rf supabase# 複製範本
cp .env.example .env
# 編輯必要設定
vim .env必要設定項目:
| 變數 | 說明 | 產生方式 |
|---|---|---|
POSTGRES_PASSWORD |
PostgreSQL 密碼 | openssl rand -base64 32 |
JWT_SECRET |
JWT 簽名密鑰 | openssl rand -base64 32 |
ANON_KEY |
匿名存取金鑰 | 使用 JWT 工具產生 |
SERVICE_ROLE_KEY |
服務角色金鑰 | 使用 JWT 工具產生 |
SITE_URL |
應用程式 URL | https://your-app.example.com |
API_EXTERNAL_URL |
API 外部 URL | https://supabase-api.example.com |
# 使用 Supabase 提供的工具
npx supabase-keys generate --secret <JWT_SECRET>輸出範例:
anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
將產生的 keys 填入 .env。
docker compose up -d# 檢查服務狀態
docker compose ps
# 測試 API
curl -s http://localhost:8000/rest/v1/ \
-H "apikey: <ANON_KEY>" | jqCloudflare Tunnel 提供免費的安全通道,無需開放防火牆。
# cloudflared config.yml
tunnel: <tunnel-id>
credentials-file: /path/to/credentials.json
ingress:
- hostname: supabase-api.example.com
service: http://localhost:8000
- hostname: supabase-studio.example.com
service: http://localhost:3000
- service: http_status:404啟動 Tunnel:
cloudflared tunnel run <tunnel-name>server {
listen 443 ssl;
server_name supabase-api.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
server {
listen 443 ssl;
server_name supabase-studio.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}如果主機上有其他服務佔用預設端口,可調整 docker-compose.yml:
| 服務 | 預設端口 | 建議替代 | 原因 |
|---|---|---|---|
| Kong | 8000 | 8001 | Portainer 使用 8000 |
| Studio | 3000 | 3001 | 開發 server 使用 3000 |
| Supavisor | 5432 | 5433 | 其他 PostgreSQL 使用 |
{
"mcpServers": {
"local-supabase": {
"type": "http",
"url": "http://localhost:54321/mcp"
},
"remote-supabase": {
"type": "http",
"url": "https://supabase-api.example.com/mcp"
}
}
}{
"permissions": {
"allow": [
"mcp__local-supabase__list_tables",
"mcp__local-supabase__execute_sql",
"mcp__local-supabase__list_migrations",
"mcp__remote-supabase__list_tables",
"mcp__remote-supabase__execute_sql"
]
},
"enabledMcpjsonServers": ["local-supabase", "remote-supabase"]
}確認 Kong 已啟用 MCP 路由。Self-hosted Supabase 的 Kong 預設包含 /mcp 端點。
如果 MCP 無法連線,檢查 Kong 設定:
docker compose logs kong | grep mcp# 1. 建立新的 migration
supabase migration new <description>
# 2. 編輯 SQL 檔案
vim supabase/migrations/YYYYMMDDHHMMSS_<description>.sql
# 3. 本地測試
supabase db reset
supabase db lint --level warning
# 4. 產生 TypeScript types
pnpm db:types方法一:docker exec(推薦)
# 複製 migration 到容器
docker cp supabase/migrations/<timestamp>_<name>.sql supabase-db:/tmp/
# 執行 migration
docker exec supabase-db psql -U postgres -d postgres -f /tmp/<timestamp>_<name>.sql方法二:psql 直連(需 VPN 或內網)
PGPASSWORD=<password> psql \
-h <host> -p 5432 -U postgres -d postgres \
-f supabase/migrations/<timestamp>_<name>.sql方法三:批次執行
# 複製所有 migration 檔案
for f in supabase/migrations/*.sql; do
docker cp "$f" supabase-db:/tmp/
done
# 依序執行
docker exec supabase-db bash -c \
'for f in /tmp/*.sql; do psql -U postgres -d postgres -f "$f"; done'# 查看 migration 狀態
docker exec supabase-db psql -U postgres -d postgres -c \
"SELECT * FROM supabase_migrations.schema_migrations ORDER BY version DESC LIMIT 5;"
# 檢查表格結構
docker exec supabase-db psql -U postgres -d postgres -c "\dt public.*"如果新增了表格或函式,需要讓 PostgREST 重新載入 schema cache:
docker compose restart rest#!/bin/bash
# /opt/supabase/scripts/backup.sh
BACKUP_DIR=/opt/supabase/backups
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE=$BACKUP_DIR/supabase_$DATE.sql.gz
mkdir -p $BACKUP_DIR
# 備份資料庫
docker exec supabase-db pg_dump -U postgres -d postgres | gzip > $BACKUP_FILE
# 保留最近 30 天
find $BACKUP_DIR -name "*.sql.gz" -mtime +30 -delete
echo "Backup completed: $BACKUP_FILE"設定 Crontab:
# 每天凌晨 3 點備份
0 3 * * * /opt/supabase/scripts/backup.sh >> /var/log/supabase-backup.log 2>&1# 完整備份
docker exec supabase-db pg_dump -U postgres -d postgres > backup.sql
# 僅備份特定 schema
docker exec supabase-db pg_dump -U postgres -d postgres \
--schema=public --schema=your_schema \
--no-owner --no-privileges > schema_backup.sql
# 壓縮備份
docker exec supabase-db pg_dump -U postgres -d postgres | gzip > backup.sql.gz# 還原完整備份
docker exec -i supabase-db psql -U postgres -d postgres < backup.sql
# 還原壓縮備份
gunzip -c backup.sql.gz | docker exec -i supabase-db psql -U postgres -d postgres
# 停用 FK 檢查後還原(避免順序問題)
docker exec -i supabase-db psql -U postgres -d postgres << 'EOF'
SET session_replication_role = 'replica';
\i /tmp/backup.sql
SET session_replication_role = 'origin';
EOF| 操作 | 指令 |
|---|---|
| 查看服務狀態 | docker compose ps |
| 查看日誌 | docker compose logs -f <service> |
| 重啟單一服務 | docker compose restart <service> |
| 重啟所有服務 | docker compose restart |
| 進入 PostgreSQL | docker exec -it supabase-db psql -U postgres |
| 查看 migration | SELECT * FROM supabase_migrations.schema_migrations; |
服務名稱對照:
| 服務 | 名稱 |
|---|---|
| PostgreSQL | db |
| PostgREST | rest |
| Kong | kong |
| Studio | studio |
| Auth | auth |
| Storage | storage |
| Realtime | realtime |
# API 健康檢查
curl -s https://supabase-api.example.com/rest/v1/ \
-H "apikey: <ANON_KEY>" | jq
# PostgreSQL 連線測試
docker exec supabase-db pg_isready -U postgres
# 查看資料庫連線數
docker exec supabase-db psql -U postgres -c \
"SELECT count(*) FROM pg_stat_activity;"# PostgREST 日誌
docker compose logs -f rest
# Kong 日誌
docker compose logs -f kong
# PostgreSQL 日誌
docker compose logs -f db
# 查看慢查詢(需啟用 pg_stat_statements)
docker exec supabase-db psql -U postgres -c \
"SELECT query, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY mean_exec_time DESC LIMIT 10;"# 1. 備份
./scripts/backup.sh
# 2. 拉取最新映像
docker compose pull
# 3. 停止服務
docker compose down
# 4. 啟動新版本
docker compose up -d
# 5. 驗證
curl -s https://supabase-api.example.com/rest/v1/ \
-H "apikey: <ANON_KEY>"# 指定版本標籤
docker compose down
docker compose -f docker-compose.yml up -d如需使用特定版本,修改 docker-compose.yml 中的映像標籤。
Self-hosted 沒有
supabase migration repair可用,需手動處理。
建議為每個 migration 建立對應的回滾腳本:
supabase/
├── migrations/
│ └── 20250101000000_create_users.sql
└── rollback/
└── 20250101000000_rollback.sql
# 1. 執行回滾 SQL
docker exec -i supabase-db psql -U postgres -d postgres \
< supabase/rollback/<timestamp>_rollback.sql
# 2. 更新 migration 狀態(可選)
docker exec -i supabase-db psql -U postgres -d postgres -c \
"DELETE FROM supabase_migrations.schema_migrations WHERE version = '<timestamp>';"# 停止服務
docker compose down
# 移除資料卷
docker volume rm supabase_db-data
# 重新啟動
docker compose up -d
# 還原備份
docker exec -i supabase-db psql -U postgres -d postgres < backup.sql| 問題 | 原因 | 解法 |
|---|---|---|
| MCP 無法連線 | Kong 未啟用 MCP 路由 | 檢查 Kong 設定與日誌 |
| Migration 執行失敗 | 權限不足或語法錯誤 | 檢查 PostgreSQL 日誌 |
| Studio 無法存取 | Tunnel/Nginx 設定錯誤 | 檢查 proxy 設定 |
| PostgREST 找不到新表格 | Schema cache 未更新 | docker compose restart rest |
| 備份檔案過大 | 資料量增長 | 設定增量備份或壓縮 |
| 連線數過多 | 連線未正確關閉 | 檢查應用程式連線池設定 |
| REST API 間歇中斷 | PostgREST pool 重建 | 見 §10.1 MCP/Studio 觸發問題 |
| Tunnel 斷線 error 1033 | SSH 傳輸頻寬飽和 | 見 §10.2 SSH 傳輸安全 |
根因:Studio(包含透過 Studio port 3001 的 MCP)每次 schema introspection 會觸發 NOTIFY pgrst,導致 PostgREST 丟棄並重建所有 connection pool。在密集查詢時(>5 次/分鐘),PostgREST 會反覆重建 pool,造成 REST API 間歇中斷。
預防:
- 生產環境的 MCP 查詢限制 ≤5 次/對話
- 禁止使用 Agent/subagent 自動化批量查詢 production MCP
- 禁止直接存取 Kong port 8001(會觸發相同 introspection)
診斷:
docker logs supabase-rest --tail 50 | grep "schema cache"
# 大量 "Schema cache loaded" 訊息 = pool 反覆重建若 Supabase 主機同時承載 Cloudflare Tunnel(cloudflared),大量 SSH 傳輸(pg_dump 串流、大檔案 SCP)會飽和 Tailscale relay 頻寬,導致 cloudflared DNS timeout → 所有 tunnel edge 連線斷開 → 全站不可用(error 1033)。
預防:
- 禁止 SSH 串流 pg_dump 到本機(
ssh host 'pg_dump ...' > local.sql) - 禁止平行 SSH/SCP 連線
- Dump 資料:先在遠端產生檔案,再於非上班時間 SCP 回本機
恢復 SOP:
ssh <host> 'journalctl -u cloudflared --since "5 min ago" --no-pager'- 若 Tunnel 斷線:
ssh <host> 'sudo systemctl restart cloudflared' - 檢查 PostgREST:
ssh <host> 'docker logs supabase-rest --tail 10' - 等待 pool 重建完成(通常 <1 分鐘)
supabase db reset 的 seed.sql 不支援 pg_dump 原生的 COPY ... FROM stdin 格式。正確做法:
-- seed.sql 開頭
SET session_replication_role = replica; -- 停用 FK 檢查和 trigger
TRUNCATE table1, table2, table3 CASCADE; -- 清空目標表
-- 使用 INSERT 格式(非 COPY)
INSERT INTO table1 (...) VALUES (...);
-- seed.sql 結尾
SET session_replication_role = DEFAULT; -- 恢復- 使用強密碼(至少 32 字元隨機字串)
- 限制資料庫外部存取(只允許 Kong Gateway)
- 定期更新 Docker 映像
- 設定 SSL/TLS(Cloudflare 或自簽憑證)
- 監控異常登入嘗試
- 定期測試備份還原流程
- 設定防火牆規則
- Studio 設定強密碼或限制 IP 存取
| 文件 | 說明 |
|---|---|
| SUPABASE_MIGRATION_GUIDE | Migration 開發流程 |
| ENVIRONMENT_VARIABLES | 環境變數設定 |
| DATABASE_OPTIMIZATION | 資料庫效能優化 |
| RLS_BEST_PRACTICES | RLS 最佳實踐 |
| Supabase MCP 指南 | MCP 整合指南 |
| Supabase Self-hosting | 官方文件 |