进阶 📋 6 个步骤 第 496 / 496 篇

Cloudflare Sandboxes 实操:给 Agent 一台随起随停的 Linux 云电脑(Durable Object 调度 + container API)

Cloudflare Sandboxes 实操:create-cloudflare 建项目、wrangler.jsonc 配 durable_object 调度、DO 里 container.start/exec 跑 Linux 命令、648ms 快启、快照持久化、凭证不出 Worker、部署不替换运行实例。

2026.10.02· 14 分钟阅读· 约 2571 字· 🖥️ Cloudflare Sandboxes / 📦 Containers

Agent 要真干活,就需要一台真电脑:装依赖、跑命令、起开发服务器、把文件留到下一轮。9 月 30 日 Cloudflare 重构了 Containers 基础设施,把「给 Agent 起沙箱」这件事的门槛打到了新低:调度策略升级后,独立基准测得容器启动中位数从 4.049 秒降到 648 毫秒——快到 Agent 可以为单个任务即时开一台、用完即弃,不用再预热容器池。本篇按官方 get-started 逐步跑通最小链路:一个 Worker 里的 Durable Object 启动 Linux 微虚拟机、执行你 POST 过去的命令、返回输出;再延展到快照持久化与凭证隔离这两个生产必需项。

🎯 适合人群:需要安全执行 Agent 生成代码或命令的开发者——编码 Agent 后端、代码解释器、评测流水线都适用。前置要求:Cloudflare 账号(Workers Paid 套餐)、Node.js 16.17+(建议用 nvm/Volta 管理)、本地开发需安装并运行 Docker。

先理解:两种沙箱与「DO 即控制器」的架构

Cloudflare 的沙箱体系有两种环境,都通过 Worker 访问。Containers:你提供镜像,实例是带独立内核与网络的完整 Linux 微虚拟机(microVM),能跑任何语言、保持进程常驻,公网流量只能经你的 Worker 到达实例;Dynamic Workers:运行时动态加载一段不受信任的 JS/Python/WASM 代码作为新 Worker 执行,适合纯代码解释场景。本篇走 Containers 路线。架构上的关键设计是每个容器实例都挂着自己的 Durable Object——一个持久化、可编程的控制器,负责实例生命周期与出站流量。新的 durable_object 调度策略(公测)把镜像与实例规格的选择下沉到代码里:过去每种「镜像 × 规格」组合都要单独部署一个 Containers 应用,现在一个 DO 里一个 if 语句就能按任务起 Node 或 Python 环境,发布策略也从运维配置变成了几行应用逻辑。

durable_object 调度策略目前是公测特性。官方文档明确标注其状态为 public beta,API 细节可能随版本调整;本教程命令与配置以 developers.cloudflare.com/sandbox 当前版本为准,生产采用前先核对官方文档的最新状态与限制。

Step 1:创建 Worker 项目

1 一条 create-cloudflare 命令起项目
# 创建 Worker 项目(TypeScript,不自动部署、不初始化 git)
npm create cloudflare@latest -- sandbox-linux --category=hello-world --type=hello-world --lang=ts --no-deploy --no-git --no-agents

# 进入项目目录
cd sandbox-linux

官方脚手架一条命令生成最小 Worker 项目,参数里 --no-deploy --no-git 让你保留对部署与版本控制的主动权。Wrangler 要求 Node 16.17.0 或更高,官方建议用版本管理器避免权限问题。生成后先看一眼目录结构:wrangler.jsonc 是配置中心,src/index.ts 是即将改写的入口。接下来两步分别动这两个文件,把「Hello World」改造成「能起 Linux 容器的沙箱」。

Step 2:配置 wrangler.jsonc——容器、DO 绑定与导出

2 三个配置块各司其职,compatibility_date 写当天
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "sandbox-linux",
  "main": "src/index.ts",
  // 改成当天的日期
  "compatibility_date": "2026-10-01",
  "observability": { "enabled": true },
  "upload_source_maps": true,
  "containers": [
    {
      "class_name": "MyContainer",
      "scheduling_policy": "durable_object"
    }
  ],
  "durable_objects": {
    "bindings": [
      { "class_name": "MyContainer", "name": "MY_CONTAINER" }
    ]
  },
  "exports": {
    "MyContainer": {
      "type": "durable-object",
      "storage": "sqlite"
    }
  }
}

配置里三个块要一起看懂:containers 声明哪个类管容器,并指定 durable_object 调度策略(新架构的核心,让 DO 在运行时代码里决定镜像与规格);durable_objects.bindings 把 MyContainer 类绑定为名为 MY_CONTAINER 的环境绑定,Worker 里通过 env.MY_CONTAINER 访问;exports 声明该 DO 类使用 SQLite 存储。官方示例的 compatibility_date 写的是示例当天日期,你实际配置时写自己的当天日期即可,不必照抄。

Step 3:写 DO 代码——start 起 Linux,exec 跑命令

3 惰性启动 + 参数透传,十几行拿到一台 Linux
// src/index.ts
import { DurableObject } from "cloudflare:workers";

export class MyContainer extends DurableObject {
  async exec(argv) {
    const container = this.ctx.container;
    if (!container) {
      throw new Error("The container binding is not configured");
    }

    if (!container.running) {
      container.start({
        // Debian Trixie,自带 Node.js 24
        image: "cloudflare/debian-trixie",
        // 让实例常驻以便接受后续命令
        entrypoint: ["sleep", "infinity"],
        // 禁止沙箱内命令访问公网
        enableInternet: false,
      });
    }

    const process = await container.exec(argv);
    const output = await process.output();
    return {
      stdout: new TextDecoder().decode(output.stdout),
      exitCode: output.exitCode,
    };
  }
}

export default {
  async fetch(request, env) {
    const { argv } = await request.json();
    const sandbox = env.MY_CONTAINER.getByName("sandbox");
    return Response.json(await sandbox.exec(argv));
  },
};

这段官方示例浓缩了三个最佳实践。惰性启动:每次 exec 前检查 container.running,没在跑才 start()——648 毫秒的启动速度让「用时再开」比「预热保活」更划算,也省掉了闲置容器的费用。参数透传:Worker 把请求体里的 argv 数组直接交给 container.exec,不在代码里拼命令字符串,天然规避了命令注入的经典错误(谁可控的输入拼进 shell 谁出事)。网络默认关闭:enableInternet: false 让沙箱内代码出不了公网,Agent 生成的代码再离谱也摸不到外部世界。需要出网时(例如要装依赖)再按官方网络文档按需放开并配代理。

「沙箱里的代码可以用你放进它里面的一切」。官方安全文档的原则:放进镜像和环境变量里的凭据、数据,沙箱内代码都能碰到。敏感凭据应保留在 Worker 侧,通过「Worker 代理出站请求」的方式供给沙箱(官方 Credentials and network 指南),而不是打进镜像。

Step 4:生成类型、本地起服务

4 wrangler types + wrangler dev,本地用 Docker 跑真容器
# 生成绑定类型(读取 src 里的 MyContainer 类)
npx wrangler types

# 本地开发:容器实例跑在本机 Docker 里
npx wrangler dev

# 前置条件:
#   - Docker 已安装且正在运行
#   - Wrangler 4.141.0 或更高版本
#     (本地拉起 cloudflare/debian-trixie 镜像所需)

wrangler types 让 env.MY_CONTAINER 带上正确类型,TypeScript 项目的必需步骤。wrangler dev 的行为值得说明:本地开发时容器实例跑在你本机的 Docker 里,所以 Docker 必须先启动;这也意味着本地与线上的微虚拟机环境高度一致,联调结果可信。版本要求别忽略——旧版 Wrangler 本地拉不起 cloudflare/debian-trixie 镜像,报错先查版本。

💡 Windows 用户建议在 WSL2 里跑 wrangler dev + Docker Desktop,兼容性明显好于原生 Windows;dev 终端会打印本地 URL(默认 http://localhost:8787),下一步直接用。

Step 5:发一条命令验证全链路

5 一发 POST,看到 Linux 内核信息就算跑通
curl http://localhost:8787 --request POST \
  --json '{"argv":["uname","-a"]}'

# 预期返回:
#   JSON 里 "exitCode": 0
#   stdout 以 "Linux" 开头
#   (完整内容含内核版本与架构信息)

# 再试一条更实用的:
#   {"argv":["node","-v"]}
#   应返回 Node.js 24.x 的版本号

这条 curl 触发的完整链路是:Worker 收到 POST → env.MY_CONTAINER.getByName("sandbox") 定位到对应 DO → DO 发现容器未运行 → 以 debian-trixie 镜像启动 Linux 微虚拟机 → 执行 uname -a → 输出经 TextDecoder 转回 JSON。看到 exitCode: 0 和以 Linux 开头的 stdout,说明整条「HTTP 请求 → DO → 容器 → 命令 → 输出」链路全部打通。接下来把它变成真正的 Agent 工具只需两小步:在 Worker 里加一层鉴权,再把 argv 的来源从你的 curl 换成 Agent 的工具调用。官方还提供了「Build a coding agent runner」指南与 sandbox-sdk 仓库里的 minimal 模板(自带 Dockerfile、按 URL 名字隔离沙箱、文件读写),照着扩展即可。

Step 6:生产三件事——快照持久化、按需出网、部署不换血

6 filesystem snapshots 打包工作区,理解沙箱生命周期
# 1) 快照(public beta):
#    把工作区文件状态保存,之后的新实例
#    可从快照恢复——Agent 跨会话接着干
#    官方 Lifetime 文档说明快照带回哪些文件

# 2) 按需出网:
#    enableInternet: false 是默认姿态;
#    需要装依赖时在官方网络指南的
#    凭证/代理框架内放开,凭据不出 Worker

# 3) 部署语义(官方 Lifetime 文档):
#    wrangler deploy 不会替换正在运行的实例;
#    实例保留其启动时的镜像直到代码让它停止,
#    下一次由 DO 启动时才用新镜像

生产化要过三道认知。快照解决「Agent 的文件能不能活到明天」:把工作区状态存下来、新实例从快照恢复,长任务与多轮会话才有连续性;它同时是评测场景的利器——几百个沙箱从同一快照出发、跑完重置,状态严格一致。出网的正确姿势不是全局打开,而是按需放行加凭证代理:凭据留在 Worker,沙箱通过你的代理拿资源,泄漏面最小。部署语义最反直觉:wrangler deploy 不会重启在跑的实例,运行中的沙箱继续用旧镜像干活——这是刻意设计(Agent 不该在任务中途被换环境),灰度与回滚都变成「下一次 start 用什么镜像」的代码决策。理解这三条,沙箱才算真正接管了你的 Agent 执行层。

💡 容器按「活跃 CPU 周期」计费(官方定价页),惰性启动 + 及时停止的写法直接决定账单;用 Manage sandboxes 文档里的模式列出一个用户的沙箱、记录每个实例何时因何种原因停止,排障时省命。

预期效果与自检清单

全部做完后,你应该达到:wrangler dev 下 curl 一条 uname -a 拿到 exitCode: 0 与 Linux 输出;node -v 能返回版本号,说明镜像内工具链可用;你能说清 durable_object 调度策略与「DO 即控制器」的关系;给 Worker 加了鉴权后才对外开放;理解快照、按需出网与「部署不替换运行中实例」三条生命周期规则。把视野拉远一点:这次重构的信号意义大于参数本身——亚秒级启动让「每个任务一台一次性电脑」从奢侈品变成默认选项,Agent 的执行层从此可以像请求一样轻量地创建与销毁。你的下一步可以是把它接进自己的编码 Agent(官方 coding-agents 指南支持 Claude Code、Codex、Devin 等在沙箱内运行),或用快照搭一套可重置的评测流水线。

← 返回教程中心