Agent 要真干活,就需要一台真电脑:装依赖、跑命令、起开发服务器、把文件留到下一轮。9 月 30 日 Cloudflare 重构了 Containers 基础设施,把「给 Agent 起沙箱」这件事的门槛打到了新低:调度策略升级后,独立基准测得容器启动中位数从 4.049 秒降到 648 毫秒——快到 Agent 可以为单个任务即时开一台、用完即弃,不用再预热容器池。本篇按官方 get-started 逐步跑通最小链路:一个 Worker 里的 Durable Object 启动 Linux 微虚拟机、执行你 POST 过去的命令、返回输出;再延展到快照持久化与凭证隔离这两个生产必需项。
先理解:两种沙箱与「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 项目
# 创建 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 绑定与导出
{
"$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 跑命令
// 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:生成类型、本地起服务
# 生成绑定类型(读取 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 镜像,报错先查版本。
Step 5:发一条命令验证全链路
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:生产三件事——快照持久化、按需出网、部署不换血
# 1) 快照(public beta):
# 把工作区文件状态保存,之后的新实例
# 可从快照恢复——Agent 跨会话接着干
# 官方 Lifetime 文档说明快照带回哪些文件
# 2) 按需出网:
# enableInternet: false 是默认姿态;
# 需要装依赖时在官方网络指南的
# 凭证/代理框架内放开,凭据不出 Worker
# 3) 部署语义(官方 Lifetime 文档):
# wrangler deploy 不会替换正在运行的实例;
# 实例保留其启动时的镜像直到代码让它停止,
# 下一次由 DO 启动时才用新镜像
生产化要过三道认知。快照解决「Agent 的文件能不能活到明天」:把工作区状态存下来、新实例从快照恢复,长任务与多轮会话才有连续性;它同时是评测场景的利器——几百个沙箱从同一快照出发、跑完重置,状态严格一致。出网的正确姿势不是全局打开,而是按需放行加凭证代理:凭据留在 Worker,沙箱通过你的代理拿资源,泄漏面最小。部署语义最反直觉:wrangler deploy 不会重启在跑的实例,运行中的沙箱继续用旧镜像干活——这是刻意设计(Agent 不该在任务中途被换环境),灰度与回滚都变成「下一次 start 用什么镜像」的代码决策。理解这三条,沙箱才算真正接管了你的 Agent 执行层。
预期效果与自检清单
全部做完后,你应该达到:wrangler dev 下 curl 一条 uname -a 拿到 exitCode: 0 与 Linux 输出;node -v 能返回版本号,说明镜像内工具链可用;你能说清 durable_object 调度策略与「DO 即控制器」的关系;给 Worker 加了鉴权后才对外开放;理解快照、按需出网与「部署不替换运行中实例」三条生命周期规则。把视野拉远一点:这次重构的信号意义大于参数本身——亚秒级启动让「每个任务一台一次性电脑」从奢侈品变成默认选项,Agent 的执行层从此可以像请求一样轻量地创建与销毁。你的下一步可以是把它接进自己的编码 Agent(官方 coding-agents 指南支持 Claude Code、Codex、Devin 等在沙箱内运行),或用快照搭一套可重置的评测流水线。