· 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 的兩種方式
- 只用 supabase-js:如果程式本來就用 Supabase 的 client,或你願意改成這樣,Workers 直接透過 HTTP 呼叫 Supabase,不需要資料庫連線。我的網站就是這樣做的。
- 用 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)。這需要時間,越早做越好。
切換步驟
-
提前降 TTL:Zeabur 指南建議提前把 DNS 記錄的 TTL 降到 300 秒,並把目前所有 DNS 記錄抄下來,當作回滾用的紀錄。
-
把網域加進 Cloudflare:在 Cloudflare 新增網域,核對它掃到的 DNS 記錄,再到註冊商把 nameserver 改成 Cloudflare 給的兩組。這一步只換 DNS 代管,記錄內容不變,網站照常指向 Zeabur。
-
先用測試子網域驗證:例如把新 Worker 掛在
new.example.com,完整測過。 -
停止寫入:經過確認後,暫停 Zeabur 上的應用程式服務(資料庫不要停),讓資料不再變動。
-
最後同步一次資料:再跑一次
pg_dump/pg_restore,核對筆數。 -
切換:刪掉指向 Zeabur 的 CNAME,替 Worker 加上 Custom Domain。Cloudflare 文件寫明,已經有 CNAME 記錄的主機名稱不能直接建立 Custom Domain,所以要先刪:
{ "routes": [{ "pattern": "example.com", "custom_domain": true }] } -
從外部驗證:用手機網路開網站,手動測登入、金流、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。所以建議的做法是:
-
在 Zeabur 的 n8n 容器裡匯出 workflows:
n8n export:workflow --backup --output=backups/latest/ -
新的 n8n 用新的
N8N_ENCRYPTION_KEY啟動,匯入 workflows:n8n import:workflow --separate --input=backups/latest/ -
到每個第三方服務撤銷舊金鑰、產生新金鑰,在新的 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 工單分開提出。退款金額以實際付現的餘額為上限,贈送額度不退。所有計費項目停止後才會結算,越早停用服務,退款越快。
參考資料
- Zeabur:〈Zeabur 即將停止服務〉 https://zeabur.com/zh-TW/blogs/zeabur-is-shutting-down
- Zeabur:〈Migrating off Zeabur: hand this guide to your coding agent〉 https://zeabur.com/blogs/migrate-from-zeabur (agent 版本:https://zeabur.com/blogs/migrate-from-zeabur.md )
- Zeabur:〈服務遷移、退款與資安事件求償說明〉 https://zeabur.com/zh-TW/blogs/migration-refunds-and-security-claims
- Zeabur:〈October 2026 security incident (preliminary report)〉 https://zeabur.com/blogs/october-2026-security-incident
- Zeabur:〈Leaving Zeabur checklist〉 https://zeabur.com/blogs/leaving-zeabur-checklist
- Zeabur 文件:PostgreSQL https://zeabur.com/docs/en-US/marketplace/postgresql
- Zeabur CLI 原始碼 https://github.com/zeabur/cli
- Zeabur Open Source https://oss.zeabur.com/ 、 https://github.com/zeabur-oss/zeabur
- Velox Data:Zeabur 金鑰與備份還原 https://velox-data.com/blog/zeabur-security-keys/
- Supabase:Migrate from Postgres https://supabase.com/docs/guides/platform/migrating-to-supabase/postgres
- Supabase:Migrate from MySQL https://supabase.com/docs/guides/platform/migrating-to-supabase/mysql
- Supabase:Connecting to Postgres https://supabase.com/docs/guides/database/connecting-to-postgres
- Supabase:Row Level Security https://supabase.com/docs/guides/database/postgres/row-level-security
- Supabase:Storage S3 authentication https://supabase.com/docs/guides/storage/s3/authentication
- Supabase:Pricing https://supabase.com/pricing
- Cloudflare:Migrate from Pages to Workers https://developers.cloudflare.com/workers/static-assets/migration-guides/migrate-from-pages/
- Cloudflare:Single Page Application https://developers.cloudflare.com/workers/static-assets/routing/single-page-application/
- Cloudflare:Hyperdrive get started https://developers.cloudflare.com/hyperdrive/get-started/
- Cloudflare:Hyperdrive + Supabase https://developers.cloudflare.com/hyperdrive/examples/connect-to-postgres/postgres-database-providers/supabase/
- Cloudflare:Workers limits https://developers.cloudflare.com/workers/platform/limits/
- Cloudflare:Workers pricing https://developers.cloudflare.com/workers/platform/pricing/
- Cloudflare:Secrets https://developers.cloudflare.com/workers/configuration/secrets/
- Cloudflare:Cron Triggers https://developers.cloudflare.com/workers/configuration/cron-triggers/
- Cloudflare:Queues get started https://developers.cloudflare.com/queues/get-started/
- Cloudflare:Service bindings https://developers.cloudflare.com/workers/runtime-apis/bindings/service-bindings/
- Cloudflare:Custom Domains https://developers.cloudflare.com/workers/configuration/routing/custom-domains/
- Cloudflare:Durable Objects WebSockets https://developers.cloudflare.com/durable-objects/best-practices/websockets/
- Cloudflare:Workers KV 運作方式 https://developers.cloudflare.com/kv/concepts/how-kv-works/
- Cloudflare:R2 pricing https://developers.cloudflare.com/r2/pricing/
- Cloudflare:R2 + rclone https://developers.cloudflare.com/r2/examples/rclone/
- Cloudflare:Containers get started https://developers.cloudflare.com/containers/get-started/
- Cloudflare:Containers lifecycle https://developers.cloudflare.com/containers/platform-details/architecture/
- Cloudflare:Containers limits https://developers.cloudflare.com/containers/platform/limits/
- Cloudflare:Containers pricing https://developers.cloudflare.com/containers/pricing/
- Cloudflare:Containers changelog https://developers.cloudflare.com/changelog/product/containers/
- Cloudflare:Next.js on Workers https://developers.cloudflare.com/workers/framework-guides/web-apps/nextjs/
- Cloudflare:Python Workers https://developers.cloudflare.com/workers/languages/python/packages/
- Astro:Cloudflare adapter https://docs.astro.build/en/guides/integrations-guide/cloudflare/
- Hono:Cloudflare Workers https://hono.dev/docs/getting-started/cloudflare-workers
- Upstash:Cloudflare Workers https://upstash.com/docs/redis/quickstarts/cloudflareworkers
- pgloader:MySQL https://pgloader.readthedocs.io/en/latest/ref/mysql.html
- n8n:Database settings https://docs.n8n.io/hosting/configuration/supported-databases-settings/
- n8n:Encryption key https://docs.n8n.io/hosting/configuration/configuration-examples/encryption-key/
- n8n:CLI commands https://docs.n8n.io/hosting/cli-commands/