说明:本文由 Codex 根据作者提供的主题、对话素材与 Cloudflare 官方文档辅助生成,属于 AI 生成/整理内容,非作者原创。请读者自行甄别并交叉验证。
Sandbox SDK 的价值不是“替你执行一个命令”,而是把一套隔离 Linux 环境的生命周期、文件系统、进程、解释器与服务暴露能力,收敛为 Worker 中可调用的 TypeScript API。它建立在 Containers 与 Durable Objects 之上,适合 AI Agent、代码解释器、在线 IDE 和不可信代码执行。
本文使用当前 RPC 模式写示例。Sandbox SDK 的接口在持续演进,旧项目如使用 HTTP/WebSocket transport、默认 session 或 exposePort(),应先阅读文末的 2026 迁移指南。
先区分四个层次#
flowchart LR R["HTTP 请求"] --> W["你的 Worker:认证与授权"] W --> ID["sandboxId:用户或租户边界"] ID --> SDK["getSandbox()"] SDK --> DO["Sandbox Durable Object"] DO --> C["独立 Linux Container"] C --> F["文件系统 /workspace"] C --> P["进程与 shell"] C --> I["代码解释器 context"] C --> T["Tunnel 或端口服务"]
sandboxId 是稳定的对象身份,不是永久磁盘。第一次真正操作才会启动容器;容器休眠、重启或销毁后,文件、后台进程、shell session 与解释器变量都会消失。要跨生命周期保存数据,使用 R2/S3/GCS 挂载或备份,或把控制面状态放在 Durable Object SQLite。
1. 最小配置与安全的取得方式#
官方当前最小配置包含 Container、Sandbox Durable Object binding 与 SQLite migration:
{
"$schema": "./node_modules/wrangler/config-schema.json",
"name": "code-runner",
"main": "src/index.ts",
"compatibility_date": "2026-08-07",
"compatibility_flags": ["nodejs_compat"],
"containers": [
{
"class_name": "Sandbox",
"image": "./Dockerfile"
}
],
"durable_objects": {
"bindings": [
{
"name": "Sandbox",
"class_name": "Sandbox"
}
]
},
"migrations": [
{
"tag": "v1",
"new_sqlite_classes": ["Sandbox"]
}
]
}Sandbox SDK 当前配置文档仍以 migration 方式初始化该 class。一般 Durable Object 的声明式 exports 与旧 migrations 不能在同一 Worker 中混用;以所用 SDK 模板及当前官方配置为准,不要自行同时配置两种 lifecycle 流程。
Worker 入口必须重新导出 SDK 的 Sandbox class:
import { getSandbox, type Sandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
interface Env {
Sandbox: DurableObjectNamespace<Sandbox>;
}
function sandboxIdFor(tenantId: string, taskId: string) {
return "tenant-" + tenantId + "-task-" + taskId;
}
export default {
async fetch(request: Request, env: Env) {
const { tenantId, taskId } = await authenticate(request);
const sandbox = getSandbox(
env.Sandbox,
sandboxIdFor(tenantId, taskId),
{
transport: "rpc",
enableDefaultSession: false,
sleepAfter: "10m",
},
);
return Response.json({ sandboxCreated: Boolean(sandbox) });
},
} satisfies ExportedHandler<Env>;把未经鉴权的 user ID 直接拼成 sandbox ID 是常见漏洞。应先完成认证与租户授权,再由可信 Worker 生成只属于一个不可信主体的 ID。不同用户必须使用不同 sandbox;同一 sandbox 中的 session 共享文件系统、进程空间与 localhost 网络,session 不是多租户隔离边界。
2. SDK 接口地图#
下面是面向日常应用最有用的接口分组。它不是要你一次学完所有方法;应先按任务选择一组 API。
| 任务 | 常用接口 | 什么时候用 |
|---|---|---|
| 生命周期 | getSandbox()、destroy() | 取得按 ID 寻址的 sandbox;临时任务结束后主动销毁。 |
| 单次命令 | exec(command, options) | 执行固定命令,读取完整 stdout/stderr/exitCode。 |
| 后台服务 | startProcess()、getProcess()、listProcesses()、killProcess() | 启动并管理 dev server、编译器或长期运行服务。 |
| 进程日志 | getProcessLogs()、streamProcessLogs() | 诊断或把长任务输出转给浏览器。 |
| 文件 | writeFile()、readFile()、mkdir()、listFiles() | 将输入写到绝对路径,并读取生成物。 |
| 会话 | createSession()、getSession()、deleteSession() | 只有确实需要保存 cwd/env/shell 上下文时才创建。 |
| 环境变量 | setEnvVars() | 在任何命令之前设置;避免把长期密钥放进不可信容器。 |
| 代码解释器 | createCodeContext()、runCode()、deleteCodeContext() | 需要 Python/JS/TS 的交互式状态与富输出时使用。 |
| 服务访问 | tunnels.get(port)、tunnels.destroy(port) | 为 sandbox 内已监听的 HTTP 服务创建公网 URL。 |
| 持久数据 | Storage / Backups API | 挂载对象存储或备份目录,不把容器磁盘当数据库。 |
3. 一次性任务:文件 API + 固定命令 + stdin#
对于用户提交的源代码或数据,优先把内容写入文件或通过 stdin 传入;不要把用户文本插入 shell 命令字符串。
const sandbox = getSandbox(env.Sandbox, sandboxId, {
transport: "rpc",
enableDefaultSession: false,
});
await sandbox.writeFile(
"/workspace/runner.py",
[
"import json",
"import sys",
"payload = json.load(sys.stdin)",
"print(payload['name'].upper())",
].join("\n"),
);
const result = await sandbox.exec("python3 /workspace/runner.py", {
stdin: JSON.stringify({ name: "cloudflare" }),
timeout: 10_000,
});
if (!result.success) {
throw new Error("runner failed: " + result.stderr);
}
return Response.json({
output: result.stdout.trim(),
exitCode: result.exitCode,
});exec() 返回 success、stdout、stderr 与 exitCode。它的 cwd、env、stdin、timeout 与实时输出回调适合单次工作;命令超时会让调用端报错,但底层进程可能仍在容器内运行,因此需要在失败策略中考虑删除 session、杀死进程或销毁 sandbox。
RPC transport 下,writeFile() 和 readFile() 还能处理 ReadableStream,适合大文件和二进制传输,而不必把数据转成巨大的 base64 字符串。
4. 有状态构建:显式 session,而不是默认 shell#
默认 session 是兼容行为。新应用推荐 enableDefaultSession: false:没有显式 session 的调用彼此不继承 shell state。需要连续执行构建步骤时,创建一个任务专属 session:
const session = await sandbox.createSession({
id: "build",
cwd: "/workspace/app",
commandTimeoutMs: 60_000,
});
try {
const install = await session.exec("npm ci");
if (!install.success) throw new Error(install.stderr);
const test = await session.exec("npm test");
if (!test.success) throw new Error(test.stderr);
} finally {
await sandbox.deleteSession("build");
}session 只隔离 cwd、环境变量和命令执行状态。两个 session 仍可看见彼此写入的文件和启动的进程,所以不要因为“用了不同 session”就把不可信租户放进同一个 sandbox。
5. 交互式分析:Code Context 与 runCode()#
当任务是 LLM 驱动的数据分析或 notebook 式交互时,runCode() 比自己拼 shell 命令更自然。显式 context 让变量、imports 和函数在同一个活跃容器内持续存在:
const context = await sandbox.createCodeContext({
language: "python",
cwd: "/workspace",
});
await sandbox.runCode(
[
"values = [2, 3, 5, 7]",
"total = sum(values)",
].join("\n"),
{ context },
);
const result = await sandbox.runCode(
"print({'count': len(values), 'total': total})",
{ context },
);
if (result.error) {
throw new Error(result.error.value);
}
return Response.json({
logs: result.logs.stdout,
richOutputs: result.results,
});runCode() 支持 Python、JavaScript 与 TypeScript,并能返回文本、表格、图表等富输出。context 的状态只在容器仍活跃时存在;需要长期保存的计算结果应写入外部存储。任务结束时可以 deleteCodeContext(context.id) 回收资源。
6. 启动服务并暴露 URL:进程 + Tunnel#
下面是一个预览服务的典型流程:先按固定 process ID 复用或启动服务,等待端口就绪,再通过 tunnel 取得 URL。
let server = await sandbox.getProcess("preview");
if (!server) {
server = await sandbox.startProcess(
"python3 -m http.server 8080 --directory /workspace/app",
{ processId: "preview" },
);
}
await server.waitForPort(8080);
const tunnel = await sandbox.tunnels.get(8080);
return Response.json({ url: tunnel.url });tunnels.get(8080) 创建 quick tunnel,适合本地开发、演示或短期预览;容器重启后 URL 会变。指定 { name: “preview” } 可创建指向自有 zone 的 named tunnel,适合稳定 hostname、webhook 和 OAuth callback,但会创建 Cloudflare Tunnel/DNS 资源,必须在生命周期结束时清理。Tunnel API 需要 RPC transport。
7. 生命周期、持久化与安全决策#
| 问题 | 正确理解 |
|---|---|
| 同一 ID 是否同一机器/磁盘? | 是同一 Sandbox DO 身份,不保证同一活跃 Container;重启后运行态会消失。 |
| 可否把每个用户放进 session? | 不行。隔离边界是 sandbox ID;session 只是一组 shell 上下文。 |
何时 destroy()? | 临时 CI、一次性 Agent task、临时预览结束时;它会清理运行态和相关资源。 |
| 怎样保存成果? | R2/S3/GCS 挂载、备份 API,或由 Worker 把结果写到外部存储。 |
| 是否能把 API key 放进容器? | 尽量不要。应让 Worker 的 outbound handler/Service Binding 在可信侧注入凭据。 |
| 是否默认允许网络? | 不要依赖默认值。按需求关闭互联网,再只开放 allowlist 或受控出口。 |
Sandbox 提供 VM 级隔离,但隔离不代替应用认证、授权、审计、资源配额和输入验证。尤其不要把未验证用户输入拼到命令、路径、tunnel name 或 sandbox ID 中。
8. 从旧示例迁移时检查#
- 使用 RPC transport;tunnels 依赖 RPC。
- 将
exposePort()的公网 URL 场景迁到tunnels.get(),并根据稳定性选择 quick 或 named tunnel。 - 设置
enableDefaultSession: false,依赖 cwd/env 的连续任务改用显式 session。 - 把旧的 stream 专用 helper 迁到基础
exec()、readFile()、writeFile()的当前流式能力。 - 为长任务定义 timeout、进程清理和
destroy()策略。