Skip to main content

Cloudflare Containers 与 Sandbox SDK 选型:生产落地、费用和安全边界

August 30, 2026
比较 Cloudflare Workers、Containers 与 Sandbox SDK,给出当前 stable/1.0 preview 边界、真实配置、隔离执行、网络安全、成本和上线清单。

Cloudflare Containers 与 Sandbox SDK 选型:生产落地、费用和安全边界

更新日期:2026-08-30

Cloudflare Workers 擅长快速事件处理,但 AI agent 执行生成代码、在线 IDE、构建系统和需要完整 Linux 文件系统的任务,不适合硬塞进普通 Worker isolate。Cloudflare Containers 提供底层 serverless container runtime,Sandbox SDK 则在它之上封装命令、文件、进程、终端与预览 URL。

两者不是竞争产品,也不是“把 Dockerfile 上传后就自动安全”。正确关系是:Worker 负责鉴权、路由、策略和协调;Containers 负责运行任意语言或 OCI 工作负载;Sandbox SDK 为不可信代码执行提供更高层 API 和每个 sandbox 的隔离环境。

截至 2026-08-30,Containers 与 Sandbox 都要求 Workers Paid plan。Sandbox 当前 stable package 仍是 @cloudflare/sandbox,同时 Cloudflare 正在通过 @cloudflare/sandbox@next 提供 1.0 preview,并建议新项目为 1.0 做准备。preview 仍可能变化,生产项目必须锁定 package、container image 和文档版本,不能把 @next 当成稳定更新通道。

先回答:该选哪个

需求首选原因
API、鉴权、缓存、边缘路由Workers启动快,平台能力丰富,不需要完整 OS
已有 OCI 镜像、自定义 runtime、CPU/内存/磁盘需求Containers可运行任意语言和完整 Linux 工作负载
AI 生成代码、在线终端、文件操作、隔离构建Sandbox SDK在 Containers 上提供命令、文件、进程与生命周期 API
只运行少量可信 JS/TSWorkers不必承担容器冷启动、磁盘和 Durable Object 成本
多租户不可信代码Sandbox SDK + 应用控制面SDK 提供隔离,应用仍负责身份、配额、网络和 secret

直接使用 Containers 时,你需要定义 Container class、镜像、实例路由、生命周期、健康检查和应用协议。Sandbox SDK 已经为通用代码执行场景做了这些封装。反过来,如果现有服务已经暴露稳定 HTTP API,或者需要特别的守护进程、二进制和端口拓扑,直接 Containers 往往比把它改造成 Sandbox 命令调用更清晰。

一套可维护的架构

推荐把系统拆成控制面与执行面:

用户请求
  -> Worker:认证、授权、输入校验、配额、审计
  -> Durable Object:为租户或任务提供一致的实例路由
  -> Sandbox / Container:执行命令、读写临时文件、启动服务
  -> outbound policy:域名 allowlist、凭据注入、请求审计
  -> R2 / S3 / 数据库:保存真正需要持久化的结果

不要把用户 ID、shell command 和 secret 拼在一条字符串后直接执行。Worker 应先验证身份,再把可信 tenant ID 映射为稳定、不可跨租户选择的 sandbox ID;用户输入写入独立文件或通过结构化参数传递;高权限凭据留在 Worker,由 outbound handler 在允许的目标请求上注入。

一个 sandbox 可以经历 running、sleeping 和 destroyed。sleep 用于 scale-to-zero,并不等于永久存储;destroy 后本地文件不应被当作可恢复数据。需要跨生命周期保留的 workspace、artifact 和审计记录应进入 R2、S3、GCS 或数据库。

stable SDK 最小配置

下面依据当前 stable 文档,使用 @cloudflare/sandbox。安装后应提交 lockfile,不要在生产部署时解析浮动的 latest

pnpm add @cloudflare/sandbox

wrangler.jsonc 需要同时声明 container、Durable Object binding 和 migration:

{
  "containers": [
    {
      "class_name": "Sandbox",
      "image": "./Dockerfile",
      "instance_type": "lite",
      "max_instances": 5,
    },
  ],
  "durable_objects": {
    "bindings": [
      {
        "class_name": "Sandbox",
        "name": "Sandbox",
      },
    ],
  },
  "migrations": [
    {
      "new_sqlite_classes": ["Sandbox"],
      "tag": "v1",
    },
  ],
}

max_instances 必须来自容量与成本设计。开发示例写 5 不代表生产默认值;同时排队 500 个用户任务时,实例限制、429/503 处理、队列背压和超时都要由控制面明确实现。Dockerfile 则应使用与 SDK 同一 release line 的 Cloudflare Sandbox image,按当前 deploy 文档升级,避免 package 与容器 API 不匹配。

Worker 必须导出 SDK 的 Sandbox class:

import { getSandbox, type Sandbox } from "@cloudflare/sandbox";

export { Sandbox } from "@cloudflare/sandbox";

type Env = {
  Sandbox: DurableObjectNamespace<Sandbox>;
  INTERNAL_RUNNER_TOKEN?: string;
};

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const authorization = request.headers.get("authorization");
    if (
      !env.INTERNAL_RUNNER_TOKEN ||
      authorization !== `Bearer ${env.INTERNAL_RUNNER_TOKEN}`
    ) {
      return new Response("Unauthorized", { status: 401 });
    }

    const url = new URL(request.url);
    if (request.method !== "POST" || url.pathname !== "/run") {
      return new Response("Not found", { status: 404 });
    }

    const contentLength = Number(request.headers.get("content-length") ?? 0);
    if (
      !Number.isFinite(contentLength) ||
      contentLength < 0 ||
      contentLength > 65_536
    ) {
      return new Response("Payload too large", { status: 413 });
    }

    const source = await request.text();
    if (new TextEncoder().encode(source).byteLength > 65_536) {
      return new Response("Payload too large", { status: 413 });
    }

    const sandbox = getSandbox(env.Sandbox, `job-${crypto.randomUUID()}`, {
      sleepAfter: "2m",
    });

    try {
      await sandbox.writeFile("/workspace/input.mjs", source);
      const result = await sandbox.exec("node /workspace/input.mjs");

      return Response.json({
        stdout: result.stdout.slice(0, 32_768),
        stderr: result.stderr.slice(0, 32_768),
        exitCode: result.exitCode,
        success: result.success,
      });
    } finally {
      await sandbox.destroy();
    }
  },
};

这只是单租户内部 runner 的最小形状,不是公开代码执行平台模板。它展示了真实的 getSandboxwriteFileexecdestroy API,同时避免把源码拼进 shell command,并让服务端为每次任务生成独立 ID。多租户产品必须从已经验证的 session 中取得租户身份,为每个租户或任务派生独立 sandbox ID,并加入并发、CPU 时间、输出、存储和调用额度;不能允许客户端自行传入 sandbox ID。

Content-Length 也可能缺失或伪造,所以代码读取后再次检查实际字节数。真实服务还要定义命令超时、进程数、文件数、磁盘、日志脱敏和响应截断。本例的固定 bearer token 只适合受控内部入口;公网产品应使用正式身份系统、短期凭证和逐租户授权。

为什么一定要在 finally 里 destroy

stable getSandbox 支持 sleepAfter,默认空闲后可以 sleep。长任务可设置 keepAlive: true,SDK 会维持实例活跃;这也意味着忘记清理会继续占用资源。一次性任务应始终在 finally 调用 sandbox.destroy()

const sandbox = getSandbox(env.Sandbox, jobId, { keepAlive: true });

try {
  const result = await sandbox.exec("pnpm test");
  return {
    success: result.success,
    exitCode: result.exitCode,
  };
} finally {
  await sandbox.destroy();
}

exec 完成后才离开 try,因此 destroy 不会提前终止这条命令。实际后台进程应等待完成、消费日志并持久化结果后再退出 try,不能启动后立即返回并销毁实例。

keepAlive 不是无限任务许可证。仍应限制任务时长,并处理 Worker 重试、客户端断开、进程挂死和重复提交。任务 ID 应幂等;结果写入持久层后再确认完成,避免网络重试创建两份收费实例。

网络与 secret:默认不应全放行

不可信代码如果能访问任意网络,就可能扫描内网、下载恶意载荷、外传数据或消耗第三方 API 额度。Cloudflare stable SDK 支持 outbound handler 拦截 sandbox 的 HTTP/HTTPS 流量。策略应默认拒绝,再按任务开放目标域名和方法。

官方推荐的模式是让凭据留在 Worker:sandbox 只发送不带高权限 token 的请求,outbound handler 在确认目标、方法和租户授权后注入短期凭据。这样用户代码无法从文件或环境变量直接读取平台 master key。

至少执行:

  • deny metadata、loopback、私网和未批准域名。
  • 只允许业务需要的 HTTP method、port 和 content type。
  • 限制请求/响应字节数、次数、并发和总时长。
  • 日志记录目标、状态与字节数,但对 Authorization、Cookie 和正文脱敏。
  • 为第三方服务使用每租户、低权限、可撤销、短期凭据。

即便通过 startProcess(..., { env }) 传 secret,也只传该进程完成任务所需的最小值。不要把 Cloudflare API token、数据库管理员密码或模型供应商主 key 写入 workspace。

预览 URL 默认可访问,不等于私有

Sandbox 可以用 exposePort() 暴露内部 HTTP 服务。当前文档明确说明 preview URL 默认可公开访问,URL 包含随机 access token 并在生产使用 HTTPS;难猜不等于你的应用完成了授权。

如果预览内容含用户代码、数据或后台能力,还要在服务内增加应用级认证,并设置短生命周期。不要在聊天记录、analytics query、公开日志或错误追踪里泄露带 token 的完整 URL。Named tunnel 会创建持续存在的 Cloudflare Tunnel 和 DNS 记录,销毁 container 不一定删除这些 Cloudflare 侧资源;使用前应单独设计资源台账与 teardown。

stable 与 1.0 preview 怎么处理

2026-06-09 的官方 deprecation 公告已经给出迁移方向:RPC transport 是推荐默认值,旧 HTTP/WebSocket transport 将不会进入未来 major;desktop 在 stable 0.10.2 已被移除;部分 buffer/stream API 正在收敛。

团队可以这样决策:

  • 已在生产使用 stable:先按 2026 deprecation guide 清理旧 transport 和已废弃 API,锁定版本,做回归,再评估 1.0。
  • 新的原型或可快速迭代项目:可按官方建议试用 @cloudflare/sandbox@next,但要接受 preview API 变化,不能无审核自动升级。
  • 强合规或长周期生产:先用 stable 完成安全和容量验证,同时建立 1.0 迁移分支;等 1.0 stable 再切换。

代码审查时要把 stable 文档、1.0 preview 文档和旧博客分开。看到 desktop、旧 transport 或 stream variant 时先核对版本,不要凭“Cloudflare 官方示例”几个字判断仍可用。

费用如何估算

Sandbox 费用由底层 Containers 决定,此外还会产生 Worker、每个 sandbox 对应的 Durable Object,以及可选 Workers Logs 费用。Containers 当前在每月 5 美元的 Workers Paid plan 中包含:

  • 25 GiB-hours memory。
  • 375 vCPU-minutes。
  • 200 GB-hours disk。

超出后按 memory provisioned time、active CPU time 和 provisioned disk time 计费;网络 egress 按区域计费。实例收到请求或手工启动后开始产生容器用量,sleep 后停止相应运行计费。具体单价和包含量可能调整,预算必须从部署日官方 pricing 页面重算。

估算不要只拿“每次命令 2 秒”相乘。真实账单还受以下因素影响:

  • instance type 预留的 memory 和 disk。
  • 冷启动、依赖安装、模型下载和测试时间。
  • keepAlivesleepAfter 是否合理。
  • 并行峰值、失败重试和客户端重复提交。
  • artifact 上传、依赖下载与 preview egress。
  • Worker、Durable Object、日志和持久存储。

上线前做小流量 canary,把每任务容器秒数、CPU、memory、disk、egress 和失败率写入成本模型;再设置用户配额、队列上限和账单告警。没有背压的“无限并行 agent”既是费用问题,也是拒绝服务入口。

生产验收清单

Security

  • 未认证请求无法创建、复用、读取或销毁 sandbox。
  • sandbox ID 只能由服务端可信身份派生,跨租户请求返回拒绝。
  • shell 参数、文件路径、archive 解压和 preview URL 均有输入边界。
  • outbound 默认拒绝,secret 不出现在 workspace、stdout、stderr 或日志。
  • 一次性任务最终 destroy;Named tunnel、DNS 和对象存储有独立清理流程。

Performance 与成本

  • 测量冷启动、热执行、sleep/resume 和大镜像拉取时间。
  • 限制 instance type、max instances、队列长度、任务时间和输出大小。
  • 重试幂等,不因 Worker timeout 重复创建任务。
  • 费用仪表盘能按租户、任务类型和失败原因归因。

Correctness 与运维

  • package 与 container image 位于兼容 release line。
  • 本地 wrangler dev、staging 和生产的 binding/migration 一致。
  • container crash、Worker deploy、Durable Object eviction 后状态语义符合设计。
  • R2/S3 artifact、日志、审计和用户结果均能恢复。
  • 有 kill switch,可停止新任务而不影响结果查询。

Cloudflare Containers/Sandbox 最适合承担的是“受控执行面”,而不是整个产品。让 Worker 保留身份、策略和 secret,让 sandbox 的权限、生命期和网络都可以被度量与撤销,才能把 AI 代码执行从演示变成生产能力。若执行结果还要进入检索生成链路,可继续阅读本站的 Cloudflare AI Search 接入 GLM-5.3-Flash,但不要让检索服务直接拥有执行环境的管理权限。

官方资料与继续阅读