外觀

語言

· 28 分鐘閱讀 · Zeabur Zeabur 遷移 Supabase Cloudflare Workers

Zeabur 遷移完整指南(附 AI 提示詞一鍵複製):搬到 Supabase 與 Cloudflare Workers

Zeabur 12 月 31 日關站、11 月 30 日退款截止。不想看完全文,複製開頭的提示詞交給 AI 帶你搬;想自己動手,下面一步步說明怎麼把資料庫、檔案、網站、API、排程和網域搬到 Supabase 與 Cloudflare Workers。

這篇寫給手上還有服務跑在 Zeabur、必須在年底前搬家的人。Zeabur 遷移最省事的一條路,是把資料庫、檔案和登入搬到 Supabase,把網站、API、排程和網域搬到 Cloudflare。我自己的網站 kelvinwang.info 就是 Astro 跑在 Cloudflare Workers、資料放在 Supabase,下面的步驟都照這個組合寫,指令以 2026 年 11 月 3 日查到的官方文件為準。

Zeabur 為什麼關站、兩次入侵怎麼發生,我在上篇和下篇寫過了,這裡只談怎麼搬,也順便說明 Zeabur 開源出來的自架版本能不能當替代方案。

一鍵複製:把這段提示詞交給 AI 幫你搬

這篇很長,不想全部看完的話,把下面這段提示詞複製給能讀網頁、能在你電腦上執行指令的 AI coding agent(例如 Claude Code、Codex),它會照這篇和 Zeabur 官方指南一步步帶你搬,每個會改變東西的步驟都會先問你。後面的內容留著當你核對它做得對不對的依據。

請幫我把目前部署在 Zeabur 上的服務,遷移到 Supabase(資料庫、檔案、登入)加 Cloudflare Workers(網站、API、排程、網域)。

先讀這兩份文件,照裡面的步驟做:
1. Zeabur 官方遷移指南:https://zeabur.com/blogs/migrate-from-zeabur.md
2. 完整遷移步驟:https://kelvinwang.info/zh-hant/blog/zeabur-migration-supabase-cloudflare/

規則:
- 不要叫我把 Zeabur、Cloudflare、Supabase 的 token 或密碼貼進對話。請我在自己的終端機執行 npx zeabur@latest auth login 和 npx wrangler login,你用已登入的 CLI 操作。
- 先只讀不改:盤點每個專案的服務、程式碼來源、環境變數(分成我自己設的和 Zeabur 注入的)、資料庫種類與版本、Volume、網域、排程、Webhook 與 OAuth 回呼網址,列成一張表給我看。
- 依盤點結果,提出每個服務要搬到哪裡(Workers、Hyperdrive、Supabase Storage、R2、Cron Triggers、Containers 或 VPS)和理由,等我同意再動手。
- 每一個會改變東西的步驟(建立資源、匯入資料、改 DNS、刪除服務)都先說明要做什麼,等我確認。
- 新環境的金鑰和密碼一律重新產生,不要沿用 Zeabur 上的舊值;最後給我一份要撤銷的舊金鑰清單。
- 資料庫用 pg_dump 逐一匯出,不要直接還原 Zeabur 備份的 data.sql。匯入 Supabase 後,收回 public schema 對 anon、authenticated 的權限並開啟 RLS,再逐表核對筆數。
- 有 n8n 的話:只搬 workflows,用新的 N8N_ENCRYPTION_KEY 啟動,credentials 全部用新的第三方金鑰重建。
- 新服務先掛在測試子網域,等我手動測過,才切換正式網域。
- 搬完給我一份清單:每個服務的新位置、還需要我手動做的事,以及 2026 年 11 月 30 日前要送出的退款與求償工單。

Zeabur 關站時程:哪些日期一定要記住

依 Zeabur 官方公告(10 月 17 日的關站公告,以及 10 月 22 日的遷移、退款與資安求償說明):

日期 會發生什麼
2026-11-30 官方建議完成遷移的日期。餘額退款與 10 月資安事件求償,都要在這天前用 Zeabur 工單提出;求償逾期不受理,退款逾期改走 email。
2026-12-01 共享叢集上運行中的服務會暫停,仍可手動恢復來備份資料。專用伺服器與付費方案停止續約,各自在到期日關閉。
2026-12-31 所有服務停止,服務資料一併刪除。

專用伺服器要特別注意:它在自己的到期日就關機、刪資料,可能早於 12 月 31 日。到期日要去 Dashboard 看,Zeabur CLI 查不到。

Zeabur 開源了,可以自己架一套繼續用嗎

Zeabur 在 2026 年 10 月 24 日建立了 GitHub 組織 zeabur-oss,以 Apache-2.0 授權釋出「在自己的 Kubernetes 上跑的自架 PaaS」,包含 Zeabur(網頁介面)、Zeabur Identity、Zeabur Builder、Zeabur Catalog 等專案,官方介紹頁是 oss.zeabur.com。截至 11 月 3 日,最新版本是 11 月 2 日的 v0.1.2-beta.0,README 標示為 pre-alpha,寫著 API 還會變動、只在少數叢集上測試過,並提醒不要指向你承受不起弄壞的叢集。

簡單說,它是一套讓你在自己的伺服器上用類似 Zeabur 介面部署服務的工具,資料一樣要自己搬,伺服器、備份和安全也要自己顧。熟悉 Kubernetes、想保留類似操作體驗的人可以研究;想把維運交出去的人,照本文搬到託管服務會比較省事。

它也救不了現在跑在 Zeabur 託管平台上的服務:12 月 31 日之後那些服務一樣會停,資料一樣會刪。

30 秒看懂搬完的架構

  • Supabase:Postgres 資料庫(含每日備份,Pro 方案)、檔案儲存(Storage)、需要的話再加登入(Auth)。
  • Cloudflare Workers:前端靜態檔(static assets)、SSR 網站、後端 API、排程(Cron Triggers)、背景工作(Queues)。
  • Cloudflare 其他服務:DNS 與網域、R2(大量檔案、免出站流量費)、Hyperdrive(讓 Workers 快速連 Postgres)、Workers Secrets(取代 Zeabur 的環境變數)。
  • 跑不進 Workers 的長駐程式:Cloudflare Containers,或留一台小 VPS。

搬完之後,部署變成 git push 或 npx wrangler deploy,備份由 Supabase 負責,網域和憑證由 Cloudflare 自動處理。

Zeabur 元件對照表:每一樣要換成什麼

Zeabur 官方的遷移指南列出了它幫你做掉、搬家後要自己補上的東西。對照到 Supabase + Cloudflare 是這樣:

Zeabur 上的東西 換成 備註
Zeabur PostgreSQL Supabase Postgres 用 pg_dump / pg_restore 搬
Zeabur MySQL / MariaDB Supabase Postgres 用 pgloader 轉,或改用其他 MySQL 主機
Zeabur Redis Upstash Redis、Workers KV、Queues、Durable Objects 依用途選,見下文
Volume(掛載的檔案) Supabase Storage 或 R2 程式要改成用 API 讀寫檔案
從 Git 自動建置、Zeabur registry 的映像檔 Workers 建置(Workers Builds / wrangler deploy)、Containers 用自己的 Dockerfile 關站後 registry 停止服務,映像檔拉不到
前端、靜態網站 Workers static assets 靜態檔請求免費
後端 API(Node、Express 等) Workers(建議改寫成 Hono),連資料庫用 Hyperdrive
長駐服務(n8n、bot、WebSocket 伺服器) Cloudflare Containers 或 VPS
環境變數、服務間變數注入(${POSTGRES_HOST}) wrangler secret put 與綁定(bindings) 要填實際值
.zeabur.internal 私有網路 Service bindings、Hyperdrive、Containers 綁定
*.zeabur.app 網域 自己的網域掛在 Workers Custom Domain 關站後 *.zeabur.app 失效
自動 HTTPS 憑證 Cloudflare 自動處理
每日資料庫備份 Supabase Pro 每日備份(保留 7 天) Free 方案沒有備份
排程 Cron Triggers(UTC 時間)
一鍵模板(n8n 等) 官方 Docker 映像檔 + Containers 或 VPS 見 n8n 一節

開始搬之前:盤點清單和換掉所有金鑰

先列清單

每個 Zeabur 專案都列出這些項目,寫在一份文件裡:

  • 服務名稱、程式碼來源(哪個 repo、哪個分支)、執行時版本(Node、Python 版本)、啟動指令。
  • 每個服務的環境變數,分成「自己設的」和「別的服務注入的」(資料庫連線字串這類)。Zeabur CLI 的 variable list 會把兩者分開列;注意 ${VAR} 形式的值是部署時才展開,匯出來看到的可能是未展開的樣子。
  • 資料庫種類、主版本、資料量。
  • Volume 掛載路徑與大小。
  • 網域:哪些是 *.zeabur.app、哪些是自己的網域、網域是不是在 Zeabur 買的。
  • 排程工作、Webhook 回呼網址、OAuth 回呼網址、金流設定裡寫死的網址。
  • 專用伺服器的到期日。

Zeabur 也出了一份離開前的最後檢查,用 CLI 指令(例如 zeabur project list、zeabur server list、zeabur domain list-registered)找出還在計費的項目和還沒匯出的資料,退款前可以照著跑一次。

每一把金鑰都要換新,不要帶舊的過去

Zeabur 10 月的初步事件報告寫得很明白:平台資料庫裡約 321 萬筆服務環境變數(名稱和值)被讀取,共享叢集所有服務的環境變數與 secrets 也在範圍內。官方要求所有使用者更換環境變數裡的全部憑證,包括 API 金鑰、雲端金鑰、資料庫密碼、簽章與加密金鑰;8 月事件後換過的也要再換一次。

所以搬家時的原則很簡單:新環境一律用新產生的金鑰。做法是先在第三方服務(OpenAI、Stripe、AWS、Google OAuth 等)建立新金鑰,放進新環境,確認服務正常,再撤銷舊金鑰。這樣每把金鑰只換一次,也不會停機。JWT 或 session 的簽章金鑰換掉後,所有使用者會被登出一次,這是正常的。

另外兩件事:

  • Zeabur 的 GitHub App 等全部搬完再移除,不然 Zeabur 上的服務會沒辦法重新部署。
  • 如果你在 PostgreSQL 裡用的是 Zeabur 給的帳號密碼,那組密碼要視為已外洩。搬到 Supabase 後用的是 Supabase 新產生的密碼,舊的不要再用在任何地方。

讓 AI coding agent 幫忙時,token 不要貼進對話

Zeabur 的遷移指南本身就是寫給 Claude Code、Codex 這類 coding agent 讀的,官方建議的用法是對 agent 說:

Read https://zeabur.com/blogs/migrate-from-zeabur.md and help me migrate my services off Zeabur.

這份指南有幾條規則值得照做:

  • 不要把 Zeabur access token 貼進對話。官方的說法是這把 token 能存取整個帳號,貼進聊天等於交出帳號。改成在自己電腦上執行 npx zeabur@latest auth login,讓 agent 使用已登入的 CLI。
  • 在專用伺服器上,kubectl get deploy 的輸出會包含展開後的環境變數明文,要當成機密看待。
  • 同樣的道理套用到新環境:Cloudflare 用 npx wrangler login,Supabase 的資料庫密碼放在本機的環境變數或不進 git 的 .env 檔,不要讓它出現在對話紀錄裡。

Step 1:資料庫搬到 Supabase

建立 Supabase 專案,拿到連線字串

在 Supabase 建立專案時會設定資料庫密碼,這就是新的密碼。連線字串在 Dashboard 的 Connect 對話框,有三種:

Direct:              postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres
Session pooler:      postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:5432/postgres
Transaction pooler:  postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:6543/postgres

要知道的差別:

  • Direct 預設走 IPv6。你的網路如果只有 IPv4,會連不上。Pro 以上方案可以加購 IPv4 add-on。
  • Session pooler(port 5432)走 IPv4,Supabase 的 Postgres 遷移指南做 pg_dump / pg_restore 時用的就是它。家裡網路不確定有沒有 IPv6,就用這個。
  • Transaction pooler(port 6543)給 serverless 環境用,大量短連線。

從 Zeabur PostgreSQL 匯出

先從 Zeabur 服務的 Instructions 或 Networking 分頁取得對外連線資訊。如果服務沒有開放對外連線,Zeabur CLI 有 zeabur service port-forward --enable 可以打開。12 月 1 日之後共享叢集的服務會被暫停,要先手動恢復才能匯出。

本機的 pg_dump 主版本要跟來源資料庫一致(Zeabur 指南和 Supabase 指南都這樣要求)。先查版本:

psql "$ZEABUR_DB_URL" -c "select version();"

然後每個資料庫各自用 pg_dump 匯出:

pg_dump "$ZEABUR_DB_URL" \
  --format=custom \
  --no-owner \
  --no-privileges \
  --no-subscriptions \
  --file=zeabur.dump

--no-owner --no-privileges 會略過擁有者和權限設定,避免跟 Supabase 管理的角色衝突。資料量大的話,Supabase 指南用的是 --format=directory --jobs=N 平行匯出,--jobs 只能搭配 directory 格式。

不要直接用 Zeabur 備份下載的 data.sql

Zeabur 的 PostgreSQL 自動備份解開後是一個 data.sql,由 pg_dumpall 產生。Velox Data 實測發現,檔案開頭有 CREATE ROLE root; 和 ALTER ROLE root ... PASSWORD 這兩行,用 psql 還原後,root 的密碼會變回 Zeabur 上的舊密碼。Zeabur 自己的文件也寫了:還原備份會覆蓋現有資料和使用者密碼。

舊密碼已經在外洩範圍內,等於把風險一起搬進新資料庫。Zeabur 的遷移指南也建議用 pg_dump 逐一匯出資料庫,因為 pg_dumpall 的檔案可能覆蓋新角色的密碼。

如果手上只剩 data.sql,至少要在還原前刪掉角色相關的行,還原後立刻改密碼:

grep -vE '^(CREATE|ALTER) ROLE' data.sql > data.clean.sql

還原前打開檔案看一下,pg_dumpall 的輸出還可能含有建立資料庫和切換資料庫的指令,要確認資料最後會進到 Supabase 的 postgres 資料庫。

匯入 Supabase

export SUPABASE_DB_URL="postgresql://postgres.[PROJECT-REF]:[YOUR-PASSWORD]@[POOLER-HOST]:5432/postgres"

pg_restore \
  --dbname="$SUPABASE_DB_URL" \
  --no-owner \
  --no-privileges \
  --verbose \
  zeabur.dump 2>&1 | tee restore.log

psql "$SUPABASE_DB_URL" -c "VACUUM VERBOSE ANALYZE;"

依 Supabase 指南,有幾件事不會跟著搬過來:

  • 角色和權限:要自己重建。
  • 擴充套件:如果還原時出現擴充套件相關的錯誤,先在 Supabase 確認 pg_available_extensions 有這個套件,執行 CREATE EXTENSION IF NOT EXISTS 套件名; 後再還原。
  • RLS 設定:要逐表重新開啟。

匯入後第一件事:處理 Data API 的權限

這一步很多人會漏掉。Supabase 會透過 Data API 對外公開 public schema,而 Supabase 文件寫明:在公開的 schema 裡、沒開 RLS 的表,任何有權限的角色都能讀寫;在既有專案中,public 新建的表一開始就把所有權限給了 anon、authenticated 等角色。也就是說,你從 Zeabur 搬來的表,可能可以被拿著公開 publishable key 的任何人直接讀寫。

如果你的程式只透過自己的後端(Workers + Hyperdrive)連資料庫,不打算用 Supabase 的 Data API,最直接的做法是收回權限並開啟 RLS:

revoke all on all tables in schema public from anon, authenticated;
revoke all on all sequences in schema public from anon, authenticated;
revoke all on all functions in schema public from anon, authenticated;

-- 之後新建的表、序列、函式也不要自動開放
alter default privileges for role postgres in schema public revoke all on tables from anon, authenticated;
alter default privileges for role postgres in schema public revoke all on sequences from anon, authenticated;
alter default privileges for role postgres in schema public revoke all on functions from anon, authenticated;

do $$
declare r record;
begin
  for r in select schemaname, tablename from pg_tables where schemaname = 'public' loop
    execute format('alter table %I.%I enable row level security', r.schemaname, r.tablename);
  end loop;
end $$;

前三行只處理現有的物件;alter default privileges 那三行讓之後用 postgres 角色建立的新物件也不會自動開放給 anon、authenticated。之後如果要讓前端用 supabase-js 直接讀資料,再針對個別表加 grant 和 policy。

核對筆數

Zeabur 指南要求匯入後逐表比對筆數。兩邊各跑一次,結果用 diff 比:

select table_name,
  (xpath('/row/c/text()',
    query_to_xml(format('select count(*) as c from %I.%I', table_schema, table_name), false, true, '')))[1]::text::bigint as rows
from information_schema.tables
where table_schema = 'public' and table_type = 'BASE TABLE'
order by table_name;

用 MySQL 的人:pgloader 轉成 Postgres

Supabase 的 MySQL 遷移指南用 pgloader。建立 config.load:

load database
  from mysql://user:password@host/source_db
  into postgres://postgres.xxxx:password@xxxx.pooler.supabase.com:5432/postgres
alter schema 'public' owner to 'postgres';
set wal_buffers = '64MB', max_wal_senders = 0, statement_timeout = 0, work_mem to '2GB';

然後執行 pgloader config.load。目標用 Session pooler 的連線字串,帳號是含專案代號的 postgres.xxxx。這個檔案裡有明文密碼,用完就刪。

轉完要檢查的地方:

  • 表落在哪個 schema。用 \dn 和 \dt *.* 看一下,pgloader 的範例裡有 ALTER SCHEMA '...' RENAME TO '...' 可以調整。
  • 型別轉換。pgloader 預設把 tinyint(1) 轉成 boolean,0000-00-00 這種零日期會轉成 NULL,程式裡有判斷這些值的地方要跟著改。
  • SQL 語法。應用程式裡的 MySQL 專用語法(反引號、LIMIT x, y、ON DUPLICATE KEY UPDATE)要改成 Postgres 寫法。

如果改 SQL 的工作量太大,換一家 MySQL 主機也是合理的選擇,Workers 的 Hyperdrive 也支援 MySQL。

Redis 要看你拿它做什麼

Zeabur 上的 Redis 沒有一對一的替代品,依用途分:

用途 建議 原因
快取、session、計數器 Upstash Redis 在 Workers 裡用 @upstash/redis/cloudflare 透過 HTTP 連線;也支援一般 Redis client(TLS)
讀多寫少的設定、快取 Workers KV 最終一致,其他地點可能要 60 秒以上才看得到更新;不適合原子操作或同一個 key 高頻寫入
背景工作佇列 Cloudflare Queues 要改寫 producer / consumer
鎖、限流、需要強一致的狀態 Durable Objects

如果背景工作是用 Redis 佇列函式庫寫的長駐 worker 程序,又不想改寫,可以把那支程序放到 Containers 或 VPS,Redis 換成 Upstash。

Step 2:檔案和 Volume 搬到 Supabase Storage 或 R2

Workers 沒有本機硬碟,原本寫進 Volume 的檔案要改成物件儲存:

  • Supabase Storage:跟資料庫、登入整合在一起,可以用 RLS 控制誰能讀哪個檔案。適合使用者上傳的頭像、附件。
  • R2:出站流量免費,每月 10 GB 免費額度。適合大量檔案、公開下載、圖片。

兩者都相容 S3 API,可以用 rclone 搬。R2 的 rclone 設定:

[r2]
type = s3
provider = Cloudflare
access_key_id = abc123
secret_access_key = xyz456
endpoint = https://<accountid>.r2.cloudflarestorage.com
acl = private
npx wrangler r2 bucket create my-uploads
rclone copy ./volume-backup r2:my-uploads/

Supabase Storage 的 S3 端點是 https://<project_ref>.storage.supabase.co/storage/v1/s3,存取金鑰在 Storage 設定裡產生。這組金鑰有完整權限且會略過 RLS,只能放在伺服器端。

檔案怎麼從 Zeabur 拿出來:專用伺服器可以 SSH 進去,從 /var/lib/rancher/k3s/storage/ 複製。共享叢集沒有 SSH,可以用 zeabur service exec -- <指令> 在容器裡執行命令,例如打包後直接上傳,能用哪些工具取決於映像檔裡裝了什麼。

Step 3:前端和靜態網站搬到 Workers

Cloudflare 的文件說 Workers 的功能範圍比 Pages 廣,也提供從 Pages 遷移到 Workers 的指南;Astro 的 Cloudflare adapter 在 v13 已經移除 Pages 支援。新專案直接用 Workers 就好。純靜態網站只要一個 wrangler.jsonc:

{
  "name": "my-site",
  "compatibility_date": "2026-11-03",
  "assets": {
    "directory": "./dist/"
  }
}

React、Vue 這類單頁應用(SPA)加一行,讓找不到的路徑回傳 index.html:

{
  "name": "my-spa",
  "compatibility_date": "2026-11-03",
  "assets": {
    "directory": "./dist/",
    "not_found_handling": "single-page-application"
  }
}

建置完執行 npx wrangler deploy 就上線。靜態檔的請求免費、不限次數,也不計入 Workers 的請求數。

Step 4:後端 API 搬到 Workers,資料庫用 Hyperdrive

連 Supabase 的兩種方式

  1. 只用 supabase-js:如果程式本來就用 Supabase 的 client,或你願意改成這樣,Workers 直接透過 HTTP 呼叫 Supabase,不需要資料庫連線。我的網站就是這樣做的。
  2. 用 SQL driver(pg、ORM):透過 Hyperdrive。Hyperdrive 會在 Cloudflare 那邊維持連線池,Workers 每次請求不用重新建立連線。

建立 Hyperdrive 設定。Cloudflare 的 Supabase 範例建議用 Direct 連線字串,不要用 pooler,因為 Hyperdrive 自己會做連線池:

npx wrangler hyperdrive create my-db \
  --connection-string="postgresql://postgres:[YOUR-PASSWORD]@db.[PROJECT-REF].supabase.co:5432/postgres"

Hyperdrive 建立時會實際連一次資料庫驗證帳密。如果這一步連不上,可以改用 Session pooler 的字串試試。

wrangler.jsonc 加上綁定:

{
  "name": "my-api",
  "main": "src/index.ts",
  "compatibility_date": "2026-11-03",
  "hyperdrive": [
    { "binding": "HYPERDRIVE", "id": "<YOUR_HYPERDRIVE_ID>" }
  ]
}

資料庫 driver 需要 Node.js 相容模式。compatibility date 在 2026-08-04 之後會預設開啟;比這更早的日期要自己加 "compatibility_flags": ["nodejs_compat"]。

import { Client } from "pg";

export interface Env {
  HYPERDRIVE: Hyperdrive;
}

export default {
  async fetch(request, env, ctx): Promise<Response> {
    const client = new Client({ connectionString: env.HYPERDRIVE.connectionString });
    await client.connect();
    const result = await client.query("select now()");
    return Response.json(result.rows);
  },
} satisfies ExportedHandler<Env>;

不用 Hyperdrive、直接從 Workers 連 Supabase 也可以:用 Transaction pooler(port 6543),client 建在模組層級重複使用,pool size 設 1,關掉 prepared statements,並要求 SSL。

Workers 的限制要先看過

項目 Free Paid(月費 5 美元起)
請求數 每天 10 萬次 每月 1,000 萬次,之後每百萬次 0.30 美元
每次請求 CPU 時間 10 ms 預設 30 秒,可調到 5 分鐘
每個 isolate 記憶體 128 MB 128 MB
Cron Triggers(每帳號) 5 個 250 個
每個請求的同時對外連線 6 6

Free 方案每次請求只有 10 ms CPU,SSR 網站或稍重的 API 很容易超過,正式服務建議直接用 Paid。

什麼東西不適合放進 Workers

  • 需要一直開著的程序:Discord / Telegram bot 的長輪詢、自己寫的背景 worker 迴圈。
  • 需要本機檔案系統、或依賴原生函式庫的程式。
  • 單次記憶體需求超過 128 MB 的工作。
  • 原本用 Node 寫的 WebSocket 伺服器(例如 Socket.IO)。Cloudflare 上的 WebSocket 伺服器要改寫成 Durable Objects,官方建議用它的 Hibernation WebSocket API。

這些放到 Containers 或 VPS,下一節講。

Step 5:長駐服務用 Cloudflare Containers 或 VPS

Cloudflare Containers 在 2026 年 4 月 13 日正式 GA,需要 Workers Paid 方案。它的運作方式是由 Worker 控制容器:請求進到 Worker,再轉給某個容器執行個體,每個容器背後是一個 Durable Object。

要先知道的特性:

  • 硬碟預設是暫時的。容器休眠後再啟動,會拿到一顆從映像檔重建的新硬碟,之前寫的檔案都不見。要保存資料,放 Supabase 或 R2;9 月推出的檔案系統快照目前還是 public beta。
  • 閒置會休眠。sleepAfter 控制閒置多久後休眠,下一個請求進來才會再開機。
  • 映像檔必須能在 linux/amd64 執行,wrangler deploy 時本機要有 Docker 在跑。

設定範例(取自官方 Get started):

{
  "containers": [
    { "max_instances": 10, "class_name": "MyContainer", "image": "./Dockerfile" }
  ],
  "durable_objects": {
    "bindings": [{ "name": "MY_CONTAINER", "class_name": "MyContainer" }]
  },
  "exports": {
    "MyContainer": { "type": "durable-object", "storage": "sqlite" }
  }
}
import { Container } from "@cloudflare/containers";

export class MyContainer extends Container {
  defaultPort = 8080;
  sleepAfter = "10m";
}

export default {
  async fetch(request, env) {
    return env.MY_CONTAINER.getByName("main").fetch(request);
  },
};

Zeabur 上的服務本來就是 Docker 映像檔,搬到 Containers 最需要補的是 Dockerfile。Zeabur 用 zbpack 自動建置,沒有 Dockerfile 的專案要自己寫一份,執行時版本、安裝、建置、啟動指令都要跟 zbpack 原本用的一致。Zeabur 指南也提醒,latest 這類浮動標籤要固定到目前實際在跑的版本。

什麼時候選 VPS:服務要 24 小時不停(不能休眠)、需要持久硬碟、或你已經熟悉 Docker Compose。這種情況一台小 VPS 跑 Docker Compose,資料庫照樣接 Supabase,通常更簡單也更便宜。VPS 要自己處理防火牆、TLS、自動重啟和監控。

Step 6:環境變數改用 wrangler secret

Zeabur 會在同一個專案裡自動把變數注入到別的服務,例如 ${POSTGRES_CONNECTION_STRING}、${POSTGRES_HOST}(主機名稱通常以 .zeabur.internal 結尾)、ZEABUR_<PORT>_URL。Zeabur 指南把「注入的變數」列為最常見的兩個失敗點之一,搬家時要把每個變數換成新環境的實際值。

在 Workers,機密用 secret,不要寫在 wrangler.jsonc 的 vars:

npx wrangler secret put OPENAI_API_KEY

多筆一次上傳,用 JSON 或 .env 格式的檔案(每次最多 100 筆):

npx wrangler secret bulk .env.production

本機開發用 .dev.vars 或 .env,兩者擇一,記得加進 .gitignore。程式裡從 env.OPENAI_API_KEY 讀取。

再強調一次:這份 .env.production 裡放的應該是新產生的金鑰。

Step 7:排程改用 Cron Triggers

{
  "triggers": {
    "crons": ["0 1 * * *"]
  }
}
export default {
  async scheduled(controller, env, ctx) {
    // 每天 UTC 01:00,也就是台灣時間 09:00
  },
};

Cron Triggers 用 UTC 時間,台灣時間要減 8 小時。Paid 方案下,間隔小於 1 小時的排程每次最多 30 秒 CPU,間隔 1 小時以上的最多 15 分鐘。比這更重的工作,可以讓 Cron 把任務丟進 Queues,由 consumer 分批處理:

{
  "queues": {
    "producers": [{ "queue": "jobs", "binding": "JOBS" }],
    "consumers": [{ "queue": "jobs", "max_batch_size": 10 }]
  }
}
npx wrangler queues create jobs

Step 8:取代 .zeabur.internal 私有網路

Zeabur 上的服務彼此用 xxx.zeabur.internal 互連,關站後這個 DNS 會失效。在 Cloudflare 上的對應做法:

  • Worker 呼叫另一個 Worker:用 Service bindings,不經過公開網路。

    {
      "services": [{ "binding": "API", "service": "my-api" }]
    }
    

    程式裡用 env.API.fetch(request) 呼叫。

  • 連資料庫:Hyperdrive 綁定。

  • 連容器:Durable Object 綁定(上面 Containers 的 MY_CONTAINER)。

程式碼裡寫死的 .zeabur.internal 和 .zeabur.app 網址都要全部搜出來改掉:

grep -rnE 'zeabur\.(internal|app)' --exclude-dir=node_modules .

Step 9:網域與 DNS 切換,把停機時間壓到最短

網域在 Zeabur 買的

先從 Zeabur Dashboard 自助轉出到其他註冊商(例如 Cloudflare Registrar)。這需要時間,越早做越好。

切換步驟

  1. 提前降 TTL:Zeabur 指南建議提前把 DNS 記錄的 TTL 降到 300 秒,並把目前所有 DNS 記錄抄下來,當作回滾用的紀錄。

  2. 把網域加進 Cloudflare:在 Cloudflare 新增網域,核對它掃到的 DNS 記錄,再到註冊商把 nameserver 改成 Cloudflare 給的兩組。這一步只換 DNS 代管,記錄內容不變,網站照常指向 Zeabur。

  3. 先用測試子網域驗證:例如把新 Worker 掛在 new.example.com,完整測過。

  4. 停止寫入:經過確認後,暫停 Zeabur 上的應用程式服務(資料庫不要停),讓資料不再變動。

  5. 最後同步一次資料:再跑一次 pg_dump / pg_restore,核對筆數。

  6. 切換:刪掉指向 Zeabur 的 CNAME,替 Worker 加上 Custom Domain。Cloudflare 文件寫明,已經有 CNAME 記錄的主機名稱不能直接建立 Custom Domain,所以要先刪:

    {
      "routes": [{ "pattern": "example.com", "custom_domain": true }]
    }
    
  7. 從外部驗證:用手機網路開網站,手動測登入、金流、Webhook 回呼。

停機時間落在第 4 步到第 6 步之間,長短主要取決於最後一次資料同步要跑多久,事先在測試時量一次就知道。

各框架的快速筆記

Next.js

Cloudflare 目前建議用 vinext 在 Workers 上跑 Next.js,OpenNext 保留給暫時無法遷移的既有專案。vinext 仍在 beta,正式上線前先跑檢查:

npx vinext check
npx vinext init

App Router、Pages Router、Server Actions、ISR、middleware 都在支援清單上;圖片最佳化是部分支援。如果你用 coding agent,也可以先執行 npx skills add cloudflare/vinext,再請 agent 做遷移。

Astro 與純靜態網站

純靜態的 Astro 不需要 adapter,用前面 static assets 的設定即可。需要 SSR 時:

npx astro add cloudflare

這個 adapter 直接部署到 Workers,v13 已經移除 Pages 支援。我的網站就是這個組合。

Nuxt、SvelteKit、Vue、React Router 等

Cloudflare 的 Workers 框架指南都有對應頁面,照各自的指南設定 adapter。

Node.js / Express

Express 依賴 Node 的 HTTP server,在 Workers 上建議改寫成 Hono,路由寫法很接近:

npm create hono@latest my-api
# 選 cloudflare-workers 範本

環境變數和綁定從 c.env 讀。如果 API 很大、短期內改不完,先把整個 Express 服務包成 Docker 映像檔放進 Containers,之後再慢慢搬。

Python

Python Workers 支援純 Python 套件和 Pyodide 套件,官方文件的範例用的就是 FastAPI。需要 C 擴充或原生函式庫的套件不一定有對應的 WebAssembly 版本,這類專案直接用 Containers 或 VPS 跑原本的 Docker 映像檔比較實際。

n8n 和其他一鍵模板怎麼搬

n8n 放在這個架構上可行嗎

可行,但 n8n 是長駐伺服器,不能放進 Workers。資料庫可以用 Supabase Postgres,n8n 本身放 VPS 或 Containers。

我比較建議 VPS。n8n 的排程是由 n8n 程序自己觸發,容器休眠時程序沒有在跑,排程也就不會執行;要在 Containers 上跑,就得讓它一直醒著,費用會照全天計算(下面成本一節有估算)。

n8n 連 Supabase 的環境變數:

DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=<POOLER-HOST>
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=postgres
DB_POSTGRESDB_USER=postgres.<PROJECT-REF>
DB_POSTGRESDB_PASSWORD=<新密碼>
DB_POSTGRESDB_SCHEMA=n8n
N8N_ENCRYPTION_KEY=<新產生的隨機字串>

這裡用 Session pooler(n8n 是長駐程式,IPv4 網路也能連)。DB_POSTGRESDB_SCHEMA 設成獨立的 n8n schema,避免 n8n 的表落在 Supabase 對外公開的 public,記得先 create schema n8n;。

N8N_ENCRYPTION_KEY 是關鍵

n8n 用這把金鑰加密資料庫裡的第三方憑證(credentials)。一般搬家時,新的 n8n 必須沿用同一把金鑰,否則舊的 credentials 解不開。

Zeabur 的情況不一樣。事件報告指出,抽查的 607 個 n8n 實例中有 602 個把加密金鑰放在環境變數裡,等於存在裡面的第三方憑證都已外洩。官方的要求是更換 n8n 加密金鑰,並重新建立所有第三方 credentials。所以建議的做法是:

  1. 在 Zeabur 的 n8n 容器裡匯出 workflows:

    n8n export:workflow --backup --output=backups/latest/
    
  2. 新的 n8n 用新的 N8N_ENCRYPTION_KEY 啟動,匯入 workflows:

    n8n import:workflow --separate --input=backups/latest/
    
  3. 到每個第三方服務撤銷舊金鑰、產生新金鑰,在新的 n8n 裡重新建立 credentials,再逐一把 workflow 接回去。

n8n 有 export:credentials --decrypted 可以匯出明文憑證,但那些憑證已經外洩,搬過去也還是要換,不如直接重建。匯入的 workflow 預設是停用狀態,確認無誤再逐一啟用。

其他一鍵模板

如果模板部署的是開源軟體,做法跟 n8n 類似:找到該軟體官方的 Docker 映像檔和設定文件,固定版本號,放到 VPS 或 Containers,資料庫接 Supabase(如果它支援 Postgres),檔案接 R2。Zeabur 10 月事件中也列出 new-api / one-api 的使用者要撤銷上游 AI 服務金鑰並重新產生存取 token,這類服務搬家時一樣要全部換新。

驗證與切換檢查清單

切換前:

  • 每個服務在新環境都有對應,grep 不到 zeabur.app / zeabur.internal
  • 所有金鑰都是新產生的,舊金鑰列好撤銷清單
  • 資料庫逐表筆數一致
  • Supabase public 的表已經處理 RLS 或收回 anon 權限
  • Volume 的檔案已經在 Storage / R2,抽查幾個檔案能正常下載
  • 測試子網域上:登入、註冊、付款、上傳、寄信、Webhook 都手動測過
  • OAuth(Google、GitHub 等)回呼網址、金流後台的通知網址已加上新網址
  • Cron 排程的 UTC 時間換算正確
  • 確認 Supabase 備份有在跑,並實際還原過一次到另一個專案(Zeabur 指南提醒:只有 dump 檔不算測試過還原)

切換後:

  • 從外部網路開網站,HTTPS 正常
  • 看 Workers 的 logs 有沒有錯誤
  • 觀察幾天沒問題後,撤銷所有舊金鑰
  • 確認穩定後,再刪除 Zeabur 上的服務,並提出退款

回滾計畫

在 Zeabur 服務到期前,回滾的方法是:把 DNS 改回切換前記下的記錄,再用 zeabur service restart 重啟暫停的服務。切換後新環境寫入的資料不會自動回到 Zeabur,所以回滾窗口越短越好。12 月 1 日之後共享叢集會暫停,專用伺服器到期就關,能回滾的時間有限,這也是建議在 11 月底前搬完的原因。

成本大概多少(2026-11-03 查詢)

只列官方價格頁查得到的數字,美元計價:

服務 免費方案 付費方案
Supabase 0 元;資料庫 500 MB、Storage 1 GB、出站流量 5 GB、最多 2 個專案;閒置一週會暫停;沒有備份 Pro 每月 25 元起;8 GB 資料庫硬碟、Storage 100 GB、出站流量 250 GB、每日備份保留 7 天、含 10 元運算額度
Cloudflare Workers 每天 10 萬次請求、每次 10 ms CPU 每月最低 5 元;含 1,000 萬次請求與 3,000 萬 CPU 毫秒
Hyperdrive 每天 10 萬次查詢 Paid 不限查詢次數
R2 每月 10 GB 儲存、100 萬次 A 類操作、1,000 萬次 B 類操作 每 GB-月 0.015 元;出站流量免費
Queues 每天 1 萬次操作 每月含 100 萬次操作,之後每百萬次 0.40 元
Containers 無 需 Workers Paid;每月含 25 GiB-小時記憶體、375 vCPU-分鐘、200 GB-小時硬碟;台灣地區出站流量每 GB 0.05 元(含 500 GB)

一般的小型網站加 API,Supabase Pro 加 Workers Paid 大約每月 30 美元。個人專案用兩邊的免費方案也跑得動,但要接受 Supabase 閒置一週會暫停、沒有自動備份。

Containers 24 小時不休眠的估算(依上面的單價自己算,只算記憶體和硬碟,CPU 依實際使用另計):basic(1/4 vCPU、1 GiB 記憶體、4 GB 硬碟)一個月約 7 美元;standard-1(1/2 vCPU、4 GiB、8 GB)約 27 美元,都要再加 Workers Paid 的 5 美元。需要全天在線的服務,拿這個數字跟 VPS 的月租比一下再決定。

常見問題

Zeabur 關站後資料還拿得回來嗎?

依官方公告,12 月 31 日所有服務停止,服務資料一併刪除;專用伺服器在到期日就刪資料。共享叢集 12 月 1 日起暫停,但還能手動恢復來備份。現在就匯出,並把備份放在新主機以外、權限受限的地方。

Zeabur 的替代方案有哪些?

Zeabur 官方指南請 agent 比較的選項有 Railway、Coolify、k3s、Docker Compose。本文的 Supabase + Cloudflare 適合想把資料庫和伺服器維運都交給託管服務的人;有大量長駐程序的專案,Docker Compose 加 VPS 可能更直接。

只有前端網站,最快怎麼搬?

前端建置成靜態檔,寫一個只有 assets 的 wrangler.jsonc,執行 npx wrangler deploy,再把網域加成 Custom Domain。前端程式碼裡如果呼叫 *.zeabur.app 的 API,要一起改。

我的服務用 Zeabur 的 PostgreSQL,可以直接還原 Zeabur 的每日備份嗎?

可以但不建議。那份 data.sql 由 pg_dumpall 產生,會把角色和舊密碼一起還原。改用 pg_dump 匯出單一資料庫;只剩 data.sql 的話,先刪掉 CREATE ROLE / ALTER ROLE 的行。

搬家一定要把登入系統換成 Supabase Auth 嗎?

不用。資料庫裡原本的使用者表照樣搬過去,程式繼續用原本的登入邏輯即可。搬家和換登入系統分兩次做,出問題比較好查。

退款和資安求償的期限是什麼時候?

都是 2026 年 11 月 30 日,透過 Zeabur 工單分開提出。退款金額以實際付現的餘額為上限,贈送額度不退。所有計費項目停止後才會結算,越早停用服務,退款越快。

參考資料