Containers on Workers
⚠️ Workers Paid only。 每一頁 Containers 文件都掛著「Available on Workers Paid plan」。
前面二十八章都在談「怎麼在 isolate 的限制裡把事情做好」。這一章談相反的問題:什麼時候該讓工作掉出 isolate。
第 27 章已經給了一個很硬的例子 —— Workers 上沒有 eval、不能在執行期編譯 WebAssembly、原生模組連 build 都過不了。所以 ffmpeg、ImageMagick、Pandoc、既有的 Python 服務、任何依賴原生二進位的東西,在 isolate 裡是絕對做不到的。
Containers 就是這個逃生門:由 Worker 觸發、由 Cloudflare 排程、跑在你附近的一個真正的 Linux 容器。
Containers 與 Sandboxes 在 2026-04-13 GA。
29.1 心智模型:容器永遠有一扇 Durable Object 前門
Section titled “29.1 心智模型:容器永遠有一扇 Durable Object 前門”這是設定時最多人搞錯的地方,所以先講清楚。
你不能直接綁定一個容器。 容器一定掛在一個 Durable Object 類別後面 —— 那個 DO 就是容器的生命週期管理者、單一序列化點、以及你的程式碼唯一能碰到的東西。
{ "containers": [ { "class_name": "MediaContainer", // ← 指向下面那個 DO 類別 "image": "./container/Dockerfile", "instance_type": "basic", "max_instances": 5 } ], "durable_objects": { "bindings": [{ "name": "MEDIA", "class_name": "MediaContainer" }] // ↑ 出現在 env 上的是這個 ↑ 必須與上面一致 }, "migrations": [{ "tag": "v1", "new_sqlite_classes": ["MediaContainer"] }]}三個名字,兩種角色:
| 設定位置 | 值 | 意義 |
|---|---|---|
containers[].class_name | MediaContainer | 哪個 DO 類別管這個容器 |
durable_objects.bindings[].class_name | MediaContainer | 必須完全一致 |
durable_objects.bindings[].name | MEDIA | env 上出現的名字 |
實測 wrangler types 產生的結果證實了這件事:
MEDIA: DurableObjectNamespace /* MediaContainer */;env 上沒有任何叫 container 的東西。 容器不是一種 binding 類型,它是 DO 的一個屬性。
wrangler 會抓到什麼、不會抓到什麼
Section titled “wrangler 會抓到什麼、不會抓到什麼”實測四種設定錯誤,跑 wrangler deploy --dry-run:
| 錯誤 | 結果 |
|---|---|
durable_objects 的 class_name 打錯 | ✅ 明確錯誤:Your Worker depends on the following Durable Objects, which are not exported in your entrypoint file: SomethingElse. |
用 new_classes 而非 new_sqlite_classes | ❌ 通過,沒有任何警告 |
完全沒有 durable_objects binding | ❌ 通過,沒有任何警告 |
完全沒有 migrations 區塊 | ⚠️ 警告,但建議的是新的 declarative exports(第 14 章)而不是 migrations |
只有「DO 類別沒 export」這一種會被擋下來。 忘了寫 DO binding、或者用錯 migration 種類,
--dry-run都放行 —— 你的程式碼裡env.MEDIA是undefined,或者容器起不來,而 build 是綠的。
--dry-run 會真的 build image
Section titled “--dry-run 會真的 build image”一個沒有記載、但會嚴重影響 CI 的行為:當 image 指向本機 Dockerfile 時,wrangler deploy --dry-run 會實際執行 docker build。
實測在沒有 registry 存取權的環境裡跑 --dry-run:
#2 ERROR: failed to do request: Head "https://registry-1.docker.io/v2/library/node/manifests/22-alpine": ForbiddenERROR: failed to build: failed to solve: node:22-alpine: failed to resolve source metadata也就是說 --dry-run 需要 Docker daemon 在跑、需要能拉 base image。這和一般人對 --dry-run(「只檢查設定,不做副作用」)的預期完全不同。
把 image 改成一個預先建好的 image URI(registry.cloudflare.com/...)就會跳過 build —— 這是 CI 上做設定驗證的實用技巧。Containers 文件裡完全沒有提到 --dry-run。
29.2 Container 類別
Section titled “29.2 Container 類別”npm i @cloudflare/containers # 實測最新版 0.3.7(2026-06-04)import { Container, getContainer } from "@cloudflare/containers";
export class MediaContainer extends Container<AppEnv> { defaultPort = 8080; // 必須與 image 裡 EXPOSE / listen 的 port 一致 sleepAfter = "2m"; // 預設 "10m" envVars = { CH29_ROLE: "media" }; enableInternet = false; allowedHosts = ["r2.cloudflarestorage.com"];
override onStart() { /* ... */ } override onStop(params: { exitCode: number; reason: string }) { /* ... */ } override onError(error: unknown) { return error; }
/** 一般的 RPC 方法(第 18 章),不用經過容器的 HTTP */ async describe() { return { defaultPort: this.defaultPort, state: await this.getState() }; }}Container 繼承自 DurableObject,所以第 14 到 17 章關於 DO 的一切都適用 —— 包含 this.ctx.storage、alarm、以及 input gate 的序列化保證。
sleepAfter 與 onActivityExpired
Section titled “sleepAfter 與 onActivityExpired”實測 shipped source:
const DEFAULT_SLEEP_AFTER = '10m'; // lib/container.js:20onActivityExpired 的基底實作:
async onActivityExpired() { console.log('Activity expired, signalling container to stop'); if (!this.container.running) return; await this.stop();}這一點常被講錯(包括本系列大綱初稿)。 常見說法是「
onActivityExpired()必須呼叫stop()或destroy(),否則容器洩漏」。準確的說法是:基底實作已經幫你呼叫
stop()了。 只有當你覆寫它並且忘記停止時才會洩漏。官方 container-class 文件也是這樣寫的:「The default implementation callsstop()」,並附上警告:「If you overrideonActivityExpired(), callawait this.stop()orawait this.destroy(). Otherwise, the container does not go to sleep.」所以要不就別覆寫,要覆寫就記得
await super.onActivityExpired()。範例專案示範的是後者。
stop() 送 SIGTERM;destroy() 直接送 SIGKILL。onStop() 在程序結束後執行,收到 exitCode 與 reason('exit' | 'runtime_signal')。
平台層面另有一條:容器要被關閉時「會收到 SIGTERM,15 分鐘後才送 SIGKILL」。所以你的程序有很寬裕的時間收尾。
enableInternet 預設是開的
Section titled “enableInternet 預設是開的”這是我在寫這一章時修正自己認知的地方。實測 shipped source:
enableInternet = true; // lib/container.js:325官方 outbound-traffic 頁也是這樣寫:「By default, a Container will allow internet access, and you can set deniedHosts to disallow specific hosts or IPs.」
如果你的容器要處理不可信的輸入(使用者上傳的檔案、外部 URL),請主動關掉它並用 allowedHosts 開白名單:
enableInternet = false; // deny by defaultallowedHosts = ["r2.cloudflarestorage.com"]; // 只放行需要的官方對 allowedHosts 的說明:「當 allowedHosts 被設定時,它會變成 deny-by-default 的白名單。」
一整組出乎意料完整的 egress 攔截 API
Section titled “一整組出乎意料完整的 egress 攔截 API”@cloudflare/containers 0.3.7 的型別裡有一組很大的出站流量控制介面,而且它是有文件的 —— 只是文件在 /containers/platform-details/outbound-traffic/,不在 Container class 參考頁,所以很容易錯過:
| API | 作用 |
|---|---|
deniedHosts | 黑名單,支援 glob |
allowedHosts | 白名單(設了就變 deny-by-default) |
interceptHttps | 攔截 HTTPS。會在容器裡產生 /etc/cloudflare/certs/cloudflare-containers-ca.crt,容器必須信任它 |
static outbound | 全域攔截 handler,簽章 (request, env, ctx) |
static outboundByHost | 每個 host 一個 handler(優先於 outbound) |
static outboundHandlers | 具名 handler,可在執行期指派 |
setOutboundHandler() / setOutboundByHost() / setAllowedHosts() / allowHost() / denyHost() … | 執行期動態調整 |
這組 API 真正厲害的地方在 container → Worker 的方向。官方 workers-connections 頁:
「outbound handler 攔截來自容器的 HTTP 請求,並在 Workers runtime 裡執行,那裡你所有設定好的 binding 都可用。」 容器打一個虛擬 hostname(例如
http://my.kv/some-key),而**「容器內部不需要任何 SDK 或 client library」**。
換句話說:你可以讓一個完全不知道 Cloudflare 存在的既有服務,透過普通的 HTTP 呼叫拿到 KV、R2、D1。 對「把既有 Docker 服務搬上來」這件事,這是最有價值的一個能力,而且它藏在一個大部分人不會點進去的頁面裡。
29.3 型別不流動(第三次)
Section titled “29.3 型別不流動(第三次)”實測 getContainer(env.MEDIA, key) 直接編譯失敗:
error TS2345: Argument of type 'DurableObjectNamespace<undefined>' is not assignable toparameter of type 'DurableObjectNamespace<Container<Env>>'.原因和第 18、26 章一模一樣 —— wrangler types 產生的是:
MEDIA: DurableObjectNamespace /* MediaContainer */;類別名在註解裡。修法也一樣,手寫一層:
import type { MediaContainer } from "./index";
export interface AppEnv extends Omit<CloudflareBindings, "MEDIA"> { MEDIA: DurableObjectNamespace<MediaContainer>;}這是本系列第三次遇到同一個問題(第 18 章 service binding、第 26 章 monorepo、本章 container)。
wrangler types對任何「binding 指向一個你自己寫的類別」的情況,都只會把類別名寫進註解。 記住這個 pattern,遇到就直接寫env.ts。
三個取得 stub 的 helper
Section titled “三個取得 stub 的 helper”import { getContainer, getRandom, loadBalance } from "@cloudflare/containers";
getContainer(env.MEDIA, "tenant-42") // 依 key 決定實例 -> 同 key 同容器(熱的)await loadBalance(env.MEDIA, 3) // 在 N 個實例間分散getRandom(env.MEDIA, 3) // 隨機挑一個選哪一個取決於你的工作有沒有「親和性」:
- 有狀態或有快取(同一個租戶的模型、同一個專案的編譯快取)→
getContainer(ns, key),同 key 命中同一個熱容器。 - 無狀態的純運算(轉檔、產 PDF)→
loadBalance,避免所有請求排隊在同一個實例上。
29.4 Instance types 與計費
Section titled “29.4 Instance types 與計費”Instance types
Section titled “Instance types”從 config-schema.json 的描述字串抄下來(這是最準確的來源):
| 類型 | vCPU | 記憶體 | 磁碟 |
|---|---|---|---|
lite | 1/16 | 256 MiB | 2 GB |
basic | 1/4 | 1 GiB | 4 GB |
standard-1 | 1/2 | 4 GiB | 8 GB |
standard-2 | 1 | 6 GiB | 12 GB |
standard-3 | 2 | 8 GiB | 16 GB |
standard-4 | 4 | 12 GiB | 20 GB |
預設是 lite。上限就是 standard-4。
修正兩件事。 本系列大綱初稿寫「舊的
dev與裸standard已不存在」—— 不對。官方 limits 頁的說法是:「
dev與standardinstance type 為了向後相容而保留,分別是lite與standard-1的別名。」而實測
wrangler deploy --dry-run用instance_type: "dev",拿到的是一個明確的警告而非錯誤:▲ WARNING - The "dev" instance_type has been renamed to "lite" and will beremoved in a subsequent version. Please update your configuration to use "lite" instead.所以:是別名 + 棄用警告,不是移除。
自訂 instance type:schema 與文件不一致
Section titled “自訂 instance type:schema 與文件不一致”schema 允許一個物件形式:
"instance_type": { "vcpu": 2, "memory_mib": 8192, "disk_mb": 16000 }實測 --dry-run 接受。約束(官方):最少 1 vCPU、最多 4 vCPU、最多 12 GiB 記憶體、最多 20 GB 磁碟,且「每 vCPU 最少 3 GiB 記憶體」、「每 1 GiB 記憶體最多 2 GB 磁碟」。
⚠️ 這裡有一個 schema 與文件的矛盾。
config-schema.json的instance_type描述最後一句寫著:「Customers on an enterprise plan have the additional option to set custom limits.」
但官方 changelog(2026-01-05)說自訂 instance type 已經「available to all users」。wrangler 內建的 schema 描述是舊的。 以 changelog 為準,但部署前自己確認。
| 資源 | Workers Paid 含量/月 | 超出 |
|---|---|---|
| 記憶體 | 25 GiB-hours | $0.0000025 / GiB-秒 |
| CPU | 375 vCPU-minutes | $0.000020 / vCPU-秒 |
| 磁碟 | 200 GB-hours | $0.00000007 / GB-秒 |
egress:北美與歐洲 $0.025/GB(含 1 TB);大洋洲/韓國/台灣 $0.05/GB(含 500 GB);其他 $0.04/GB(含 500 GB)。
計費的關鍵區分,官方原文:
「CPU usage is based on active usage only」,而「Memory and disk usage are based on the provisioned resources for the instance type you select.」
也就是說 —— CPU 按實際使用計費,但記憶體與磁碟按「你選的 instance type 的配置量」計費,只要容器醒著就在算。
這句話推導出一個很重要的成本結論:閒置的容器仍然在燒記憶體與磁碟的錢,只有 CPU 是免費的。 所以
sleepAfter不是一個「反正閒著也不花錢」的參數,它直接決定你的記憶體帳單。範例把它設成"2m"而不是預設的"10m",理由就在這裡。粗略換算:一個
standard-1(4 GiB)閒置 10 分鐘 = 4 × 600 × $0.0000025 = $0.006。看起來很小,但乘上一天幾千次的觸發就不是了。
粒度:「每 10ms 主動執行時就計費一次」。開始於「請求送到容器時,或手動啟動時」,停止於「容器實例睡著之後」。
另一個修正。 大綱初稿寫「GA 起改為 active-CPU 計價」。實際上這個改變是 2025-11-21 的 changelog(「New CPU Pricing for Containers and Sandboxes」),比 2026-04-13 的 GA 早了快五個月。GA 那篇只是重述。
Containers 還會連帶產生 Workers 與 Durable Objects 的費用,那些是分開計算的。
29.5 限制
Section titled “29.5 限制”| 限制 | 值 |
|---|---|
| 每帳號並行記憶體 | 6 TiB |
| 每帳號並行 vCPU | 1,500 |
| 每帳號並行磁碟 | 30 TB |
| 每帳號 image 儲存總量 | 50 GB |
| 單一 image 大小 | 「受所選 instance type 的可用磁碟限制」 |
max_instances(每個 container app) | 預設 20 |
| 冷啟動 | 「經常落在 1-3 秒,但取決於 image 大小與程式碼執行時間」 |
| 關閉流程 | SIGTERM → 15 分鐘後 SIGKILL |
注意帳號層級的上限是用資源量(記憶體/vCPU/磁碟)表示的,不是實例數。 而且這組數字是 2026-02-25 那次「15 倍提升」之後的值 —— 更早的教學會寫 400 GiB / 100 vCPU / 2 TB(2025-09)甚至 40 GiB / 20 vCPU / 100 GB。
架構要求: 官方唯一的一句話是「Containers should be built for the linux/amd64 architecture」。
⚠️ 文件沒有說在 arm64(Apple Silicon)上建置會發生什麼事。 沒有錯誤訊息、沒有
--platform linux/amd64的指引、沒有任何 Apple Silicon 的註記。本章不臆測失敗形式,但實務上請在 Dockerfile 或 build 指令裡明確指定平台。
沒有 GPU。 我逐字搜尋過 Containers 的全文,“GPU” 一次都沒有出現;limits 頁沒有 GPU instance type,FAQ 也沒有。
網路上唯一相關的資料是 Cloudflare 2024-09-27 的一篇部落格〈Our container platform is in production. It has GPUs.〉。那篇講的是 Cloudflare 的內部平台(驅動 Workers AI、Workers Builds、Browser Rendering 的那個),文中自己寫著「we’re not ready (quite yet) to open up the platform to everyone」。不要拿它當作公開產品有 GPU 的證據。
需要 GPU 的話,第 34 章的 Workers AI 才是那條路。
29.6 本機開發與 image
Section titled “29.6 本機開發與 image”Docker 是必需的。 官方:「要在本機開發啟用 Container 的 Worker,你必須先確保安裝了 Docker 相容的 CLI 工具與 Engine。」部署時也一樣:「執行 wrangler deploy 時,你必須有 Docker 在本機執行。」
image 指向本機路徑時,wrangler 會用那個 Dockerfile 建置。dev session 中按 [r] 可以重建。
registry 支援在兩種 dev runner 之間不一樣(官方原文):
「使用
wrangler dev時,本機開發支援來自 Cloudflare Registry、Docker Hub、Amazon ECR 的 image 參照。使用vite dev時,支援 Docker Hub 與 Amazon ECR 這類外部 registry,但vite dev無法直接從 Cloudflare Registry 拉取。」
支援的 registry:
registry.cloudflare.com(Cloudflare 託管,背後是 R2)- Docker Hub(public + private,2026-03-24 起)
- Amazon ECR(僅 private)
- Google Artifact Registry(僅
*-docker.pkg.dev,2026-07-01 起) - GHCR 不在支援清單上(也沒有明文否認)
私有 registry 認證走 wrangler containers registries configure,憑證存在 Secrets Store。
image_build_context(預設是 image 所在目錄)與 image_vars(等同 docker build --build-arg)這兩個欄位只記載在 Workers 的 Wrangler 設定參考頁,不在 Containers 文件裡。
29.7 部署:rollout 的一個真陷阱
Section titled “29.7 部署:rollout 的一個真陷阱”Worker 程式碼與容器實例不是同時更新的。官方 rollouts 頁:
「Worker 程式碼會立即更新,而 Container 實例採用滾動部署策略更新。」
rollout_step_percentage 預設是 [10, 100] —— 先更新 10%,再更新剩下的 90%。也可以給單一數字或陣列。rollout_active_grace_period(預設 0)是「一個活躍的容器實例變成可更新之前,最少要等待的秒數」。
想要立刻全量:wrangler deploy --containers-rollout=immediate。
官方那句警告值得原樣抄進你的 code review checklist:
「因為 Worker 程式碼會立即更新、而容器實例是逐步 rollout,請在 rollout 完成前,讓變更在兩個版本之間保持向後相容。」
具體來說:如果你同時改了 Worker 送進容器的請求格式和容器的解析邏輯,rollout 期間新 Worker 會打到舊容器。這和資料庫 migration 的 expand-contract 是同一個問題。
"constraints": { "regions": ["WNAM", "ENAM"], "jurisdiction": "eu"}九個 region:ENAM、WNAM、EEUR、WEUR、APAC、SAM、ME、OC、AFR。官方警告:「容量有限的 region(ME、OC、AFR)不能單獨使用。」
jurisdiction 支援 eu(→ EEUR、WEUR)與 fedramp(→ ENAM、WNAM)。同時指定兩者時,region 必須在該 jurisdiction 的範圍內。
schema 裡有、文件裡沒有的欄位
Section titled “schema 裡有、文件裡沒有的欄位”config-schema.json 的 ContainerApp 有幾個官方文件完全沒提到的欄位:
| 欄位 | 狀態 |
|---|---|
scheduling_policy(default / moon / regional) | 文件完全查無 |
trusted_user_ca_keys | 文件完全查無 |
這兩個存在於 wrangler / Cloudchamber 的 API schema,但在 developers.cloudflare.com 上搜不到任何說明。不要用它們 —— 沒有文件的設定沒有相容性承諾。
29.8 SSH 進容器
Section titled “29.8 SSH 進容器”這是一個很多人不知道存在的能力:
"containers": [{ "class_name": "MediaContainer", "image": "./container/Dockerfile", "authorized_keys": ["ssh-ed25519 AAAA..."]}]npx wrangler containers instances # 找到 instance idnpx wrangler containers ssh <INSTANCE_ID>只支援 ssh-ed25519 金鑰類型。安全模型官方原文:
「SSH 不會在 Container 上開放一個公開可存取的 port。唯一的連線方式是透過 Wrangler 的
wrangler containers ssh,它會對你的 Cloudflare 帳號進行認證。」
也支援 ProxyCommand(2026-05-28 起):
ssh -o ProxyCommand="wrangler containers ssh %h" cloudchamber@<INSTANCE_ID>⚠️
ssh.enabled的預設值,文件與 schema 打架。官方 SSH 頁:「
ssh.enabled屬性只控制你能否透過 Wrangler SSH 進 Container。它預設為true。」但
config-schema.json裡:"enabled": { "type": "boolean", "description": "...", "default": false }兩者矛盾。 SSH 是 2026-03-12 加入、2026-05-12 改為預設啟用的;看起來 schema 的預設值沒跟上。如果你要明確關掉它,就明確寫
"ssh": { "enabled": false },不要依賴任何一邊的預設。
29.9 Sandboxes 是另一個產品
Section titled “29.9 Sandboxes 是另一個產品”@cloudflare/sandbox(實測最新版 0.12.4,2026-07-21)常常和 Containers 被混為一談。它們的關係是:
「Sandbox SDK 讓你在隔離的環境裡安全地執行不可信的程式碼」,而且「建立在 Containers 之上」。
- Containers = 你自己的 image、你自己的服務。
- Sandboxes = 執行別人的程式碼(AI agent 產生的、使用者提交的),有 Python/JS/TS 的持久 code interpreter、預覽 URL、瀏覽器 PTY 終端、workspace 快照。
兩者同一天 GA(2026-04-13),Sandboxes 的計費走底下 Containers 的價目。有自己獨立的文件樹(/sandbox/)。
選擇很單純:執行的是你自己寫的東西 → Containers;執行的是你不信任的東西 → Sandboxes。
29.10 LinkForge:什麼時候用、什麼時候不用
Section titled “29.10 LinkForge:什麼時候用、什麼時候不用”LinkForge 目前為止的功能,沒有一個需要容器。這一點本身就是結論 —— 容器是逃生門,不是預設選項。
會需要的情況:
| 需求 | 為什麼 isolate 做不到 |
|---|---|
| 批次產生高解析度 QR PNG | 需要原生影像函式庫 |
| 把分析報表輸出成 PDF | LaTeX / wkhtmltopdf 是原生二進位 |
| 匯入使用者上傳的 CSV/Excel | 大檔案超過 128 MB 記憶體上限(第 12 章) |
| 執行使用者提供的 webhook 轉換腳本 | 第 27 章:沒有 eval、不能執行期編譯 WASM → 而且這應該用 Sandboxes |
正確的形狀:Queue → Container,不是 Request → Container
Section titled “正確的形狀:Queue → Container,不是 Request → Container”// ❌ 不要:使用者按下按鈕,同步等一個 1-3 秒冷啟的容器app.post("/api/export", async (c) => { const stub = getContainer(c.env.MEDIA, c.get("session").tenantId); return stub.fetch(new Request("http://container/pdf")); // 使用者等好幾秒});
// ✅ 要:立刻回應,工作丟給 Queue(第 19 章),consumer 才碰容器app.post("/api/export", async (c) => { const jobId = crypto.randomUUID(); await c.env.EXPORTS.send({ jobId, tenantId: c.get("session").tenantId }); return c.json({ jobId }, 202);});// Queue consumerexport default { async queue(batch: MessageBatch<ExportJob>, env: AppEnv) { for (const msg of batch.messages) { // 依租戶分流 -> 同一個租戶的工作命中同一個熱容器 const stub = getContainer(env.MEDIA, msg.body.tenantId); const res = await stub.fetch(new Request("http://container/pdf", { method: "POST", body: JSON.stringify(msg.body), })); if (!res.ok) { msg.retry(); continue; } await env.BUCKET.put(`exports/${msg.body.jobId}.pdf`, res.body); msg.ack(); } },};理由有三個,全部來自本章與前面章節的實測:
- 冷啟 1-3 秒。 放在請求路徑上就是 1-3 秒的 p99。
- Queue 天生就有重試與 DLQ(第 19 章),容器崩掉時你不必自己實作。
- 依租戶當 key 讓同一個租戶的連續工作命中熱容器,攤掉冷啟成本 —— 這正是
getContainer(ns, key)而非loadBalance的理由。
再加上一條成本上的理由:sleepAfter 決定記憶體帳單(29.4),而批次化的工作會讓容器「醒著的時間集中」,比零星喚醒划算得多。
29.11 本章實測結論彙整
Section titled “29.11 本章實測結論彙整”| # | 結論 | 影響 |
|---|---|---|
| 1 | 容器一定掛在 DO 後面;env 上出現的是 DO binding 的 name,不是 class_name | 最常見的設定錯誤 |
| 2 | wrangler types 產生 MEDIA: DurableObjectNamespace /* MediaContainer */; | 類別名在註解裡 |
| 3 | getContainer(env.MEDIA, k) 因此編譯失敗(TS2345),要手寫 env.ts | 本系列第三次遇到型別不流動(第 18、26 章) |
| 4 | DO class_name 打錯 → wrangler 明確報錯 | 唯一會被擋下來的設定錯誤 |
| 5 | 用 new_classes 而非 new_sqlite_classes → --dry-run 通過,無警告 | 靜默失敗 |
| 6 | 完全沒有 durable_objects binding → --dry-run 通過,無警告 | env.MEDIA 會是 undefined |
| 7 | 沒有 migrations → 警告建議改用 declarative exports(第 14 章) | migrations 不是唯一寫法 |
| 8 | --dry-run 會真的執行 docker build | CI 需要 Docker + registry 存取;Containers 文件從未提及 --dry-run |
| 9 | 改用預建 image URI 可跳過 build | CI 驗證設定的實用技巧 |
| 10 | sleepAfter 預設 "10m"(shipped source 的 DEFAULT_SLEEP_AFTER) | |
| 11 | onActivityExpired 基底實作已呼叫 this.stop() | 「不呼叫就洩漏」的說法只在你覆寫時成立 |
| 12 | enableInternet 預設是 true(source 與文件一致) | 處理不可信輸入時要主動關掉 |
| 13 | 完整的 egress 攔截 API 有文件,但在 /platform-details/outbound-traffic/ | 容易錯過;container→Worker 呼叫不需要容器內裝 SDK |
| 14 | dev / standard 是 lite / standard-1 的別名,不是移除 | 實測 dev 得到明確的改名警告 |
| 15 | 上限是 standard-4:4 vCPU / 12 GiB / 20 GB | 預設是 lite |
| 16 | 自訂 instance type 物件形式 --dry-run 接受 | |
| 17 | schema 說自訂尺寸是 enterprise-only,changelog 說已對所有人開放 | wrangler 內建 schema 描述過時 |
| 18 | CPU 按實際使用計費;記憶體與磁碟按 instance type 的配置量計費 | 閒置容器仍在燒記憶體錢 → sleepAfter 是成本參數 |
| 19 | active-CPU 計價是 2025-11-21 上線,不是 GA 時 | 比 GA 早近五個月 |
| 20 | 帳號上限以資源量表示:6 TiB 記憶體 / 1,500 vCPU / 30 TB 磁碟;image 儲存 50 GB | 2026-02-25 的 15 倍提升後值 |
| 21 | 冷啟「經常 1-3 秒」;關閉是 SIGTERM → 15 分鐘 → SIGKILL | 不要放在請求路徑上 |
| 22 | 架構必須 linux/amd64;arm64 上會怎樣,文件完全沒說 | 明確指定 --platform |
| 23 | 沒有 GPU(全文搜尋 0 次命中) | 2024 年那篇 GPU 部落格講的是內部平台 |
| 24 | wrangler dev 與 vite dev 的 registry 支援不同 —— vite dev 拉不了 Cloudflare Registry | |
| 25 | GHCR 不在支援清單上 | 也未明文否認 |
| 26 | rollout 期間 Worker 立即更新、容器逐步更新 | 變更必須跨版本向後相容 |
| 27 | scheduling_policy(含 moon)與 trusted_user_ca_keys 在 schema 裡但文件查無 | 不要使用 |
| 28 | ssh.enabled 預設值,文件說 true、schema 說 false | 明確寫出來,不要靠預設 |
| 29 | Sandboxes 是建在 Containers 上的另一個產品(0.12.4),有獨立文件樹 | 執行不可信程式碼用它 |
29.12 動手練習
Section titled “29.12 動手練習”- 故意把
migrations從new_sqlite_classes改成new_classes,確認--dry-run真的不會擋,然後部署看它在哪一步失敗。 - 用預建 image URI 取代 Dockerfile 路徑,量測
--dry-run的時間差 —— 這就是 29.1 那個 CI 技巧的價值。 - 設
enableInternet = false加一個allowedHosts,在容器裡curl一個不在白名單的 host,觀察失敗形式。 - 用
static outboundByHost讓容器透過http://my.kv/<key>讀 KV,驗證「容器內不需要任何 SDK」這個說法。 - 把
sleepAfter從"10m"改成"30s",用 dashboard 的 metrics 比較一天下來的 GiB-hours 差異。 - 在 Apple Silicon 上不加
--platform建一個 image 並部署,記錄實際的失敗訊息 —— 文件沒寫,這個資訊值得寫成 issue 回報。
- Containers overview · Get started · FAQ
- Container class · Outbound traffic · Connect to Workers and Bindings
- Limits and instance types · Pricing · Architecture
- Image management · Local dev · Rollouts · Placement · SSH
- Changelog:GA 2026-04-13 · CPU 計價 2025-11-21 · 資源上限提升 2026-02-25 · Outbound Workers 2026-03-26
- Sandbox SDK
- 設定 schema 與預設值來源:
node_modules/wrangler/config-schema.json(4.118.0)與node_modules/@cloudflare/containers/dist/(0.3.7)
下一章(第 30 章):Browser Run(原 Browser Rendering)—— 當你需要的不是任意 Linux 而是一個 headless 瀏覽器時,平台已經幫你託管好了。