
Cloudflare Sandbox SDK 实战从 5 分钟搭沙箱到生产级隔离执行环境【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills本文面向需要在 Cloudflare Workers 上运行不受信任代码的开发者。读完你能在 5 分钟内接好 Cloudflare Sandbox SDK 的沙箱跑通命令执行、文件读写、端口暴露、多租户会话并把容器成本控制在预期内。第一次跑通5 分钟拿到一个容器沙箱就是一个带门牌号的房间理解 Cloudflare Sandbox 最直观的方式是酒店房间getSandbox传入的字符串就是门牌号同一个号码永远进同一间房不同号码之间互不相干。每间房背后由一个 Durable Object 管理状态再挂一个真正干活的容器。隔离到什么程度文件系统、进程、网络各自独立房间与房间之间无法直接通信。没人使用时房间会关灯休眠再次访问自动开门关灯时长可以由你调。下面这段 Worker 入口代码演示最小可用形态import { getSandbox, proxyToSandbox, type Sandbox } from cloudflare/sandbox; export { Sandbox } from cloudflare/sandbox; type Env { Sandbox: DurableObjectNamespaceSandbox }; export default { async fetch(request: Request, env: Env): PromiseResponse { const proxyResponse await proxyToSandbox(request, env); if (proxyResponse) return proxyResponse; const sandbox getSandbox(env.Sandbox, demo); const result await sandbox.exec(node -p 19 * 23); return Response.json({ output: result.stdout }); } };⚠️proxyToSandbox必须是 fetch 里的第一句话。少了它后面生成的预览地址全都无法被路由回容器。架构层面的说明可以对照仓库里的 README.md。三件套接线Worker 配置、实例规格、镜像wrangler.jsonc要交代三件事从 Dockerfile 构建容器、Durable Object 绑定加迁移、实例规格{ name: my-sandbox-worker, main: src/index.ts, compatibility_date: 2025-01-01, containers: [{ class_name: Sandbox, image: ./Dockerfile, instance_type: lite, max_instances: 5 }], durable_objects: { bindings: [{ class_name: Sandbox, name: Sandbox }] }, migrations: [{ tag: v1, new_sqlite_classes: [Sandbox] }] }规格分三档lite是 256MB / 0.5 vCPU默认standard是 512MB / 1 vCPUheavy是 1GB / 2 vCPU。max_instances限制这类镜像同时能拉起的容器数。Dockerfile 基于官方基础镜像缺什么装什么FROM docker.io/cloudflare/sandbox:latest RUN pip3 install --no-cache-dir pandas numpy EXPOSE 3000EXPOSE只对wrangler dev本地调试有用生产环境会自动开放所有端口。首次部署要构建镜像大约 2–3 分钟属正常现象。日常操作跑命令、存文件、守进程如何执行一条命令并把日志流出来exec返回五个字段stdout、stderr、exitCode、success即退出码为 0、duration。默认超时 120 秒长任务可用timeout自己放大开启stream: true后onOutput会随输出实时触发适合盯npm install这类长命令const result await sandbox.exec(bash build.sh, { cwd: /workspace/app, env: { CI: 1 }, timeout: 30000, stream: true, onOutput: (stream, data) console.log(data) });cwd固定工作目录env给命令进程注入环境变量——后者也是把密钥送进容器的推荐通道安全部分会展开讲。文件该放哪里才不丢writeFile会自动创建缺失的中间目录不用手动 mkdirreadFile返回{ content }await sandbox.writeFile(/workspace/notes/todo.md, 草稿); const { content } await sandbox.readFile(/workspace/notes/todo.md); await sandbox.listFiles(/workspace); await sandbox.deleteFile(/workspace/cache, { recursive: true });mkdir、pathExists两个工具方法也有。这里有个必踩的坑⚠️/tmp等临时路径里的文件不会幸存。容器休眠唤醒或重启之后只有/workspace下的内容保证还在持久产物一律落在这里。如何启动常驻服务并确认它就绪exec管一次性任务服务进程用startProcess返回{ id, pid, command }。进程启动不等于端口已监听必须先等就绪再对外const server await sandbox.startProcess(node server.js, { processId: api, cwd: /workspace/app, env: { PORT: 3000 } }); await server.waitForPort(3000);就绪探测有三种waitForPort(3000)等端口监听、waitForLog(/ready/)等日志匹配正则、waitForExit()等进程退出。后续管理全部凭你起的processIdconst list await sandbox.listProcesses(); await sandbox.stopProcess(api); const logs await sandbox.getProcessLogs(api);把服务挂上网预览地址与 WebSocket如何暴露容器端口并拿到地址容器内端口默认对外不可达exposePort会生成一个带鉴权令牌的公开预览地址const { url } await sandbox.exposePort(3000, { name: api-preview, hostname: request.hostname });地址形如https://3000-sandbox-abc123def456.yourdomain.com。每次调用exposePort令牌都会换发旧 URL 随即失效靠机制而非人工防住未授权访问。配套查询接口有三个isPortExposed(3000)查是否已暴露getExposedPorts(request.hostname)列出已暴露端口unexposePort(3000)撤销暴露。预览 URL 生效的四个前置条件这块最容易翻车四个条件缺一不可使用自定义域名并配好通配符 DNS*.yourdomain.com → worker.yourdomain.com不支持.workers.dev域名getSandbox传入normalizeId: truefetch handler 里先调用proxyToSandbox()。normalizeId的作用是把 ID 小写化注意它的副作用getSandbox(env.Sandbox, MyApp); // DO ID: hash(MyApp) getSandbox(env.Sandbox, MyApp, { normalizeId: true }); // DO ID: hash(myapp)这两行对应两个不同的沙箱。中途随手切换该选项等于换了一间房旧文件和进程全部留在原房间。如何把 WebSocket 请求代理进容器实时双向通道用wsConnect把握手请求直接透传到容器内的端口if (request.headers.get(Upgrade)?.toLowerCase() websocket) { const sandbox getSandbox(env.Sandbox, realtime); return await sandbox.wsConnect(request, 3000); }先靠Upgrade请求头区分 WS 握手与普通 HTTP。非 WS 请求也可以先暴露端口再把url做url.replace(https, wss)交给客户端拨号。镜像里装好依赖、声明好端口FROM docker.io/cloudflare/sandbox:latest RUN npm install -g ws EXPOSE 3000多租户工位与 AI 代码解释器如何给每个用户开独立工位createSession在沙箱内建一个执行上下文独立的 shell 状态、环境变量、工作目录、进程命名空间相当于同一间房里再隔出一个工位let session; try { session await sandbox.getSession(tenant-42); } catch { session await sandbox.createSession({ id: tenant-42, cwd: /workspace/users/tenant-42, env: { USER_ID: tenant-42 } }); } await session.exec(echo $USER_ID);会话暴露与沙箱相同的完整 APIexec、文件操作等只是状态各自为政deleteSession(tenant-42)回收工位。若要更强的隔离——文件系统、网络彻底分开——干脆让每个租户使用唯一的 sandbox ID平台保证沙箱之间不能直接通信。仓库里的 patterns.md 有一段现成的多租户写法可对照参考。如何让容器画图并把图片返回代码解释器是 AI Agent 场景的主力。createCodeContext建一个带初始变量的代码上下文runCode执行片段并返回富输出const ctx await sandbox.createCodeContext({ language: python, variables: { points: [3, 1, 4, 1, 5] } }); const result await ctx.runCode( import matplotlib.pyplot as plt plt.plot(points) plt.savefig(curve.png) print(len(points)) );返回值的outputs数组每项含typetext/image/html与content代码异常时error非空。变量跨次调用保留下一段runCode直接能用points。但上下文状态是易失的——容器重启休眠唤醒也算后变量全清需要重建上下文。如何把 R2 存储挂成目录跨容器持久化数据可以把 Bucket 挂成目录await sandbox.mountBucket(env.DATA_BUCKET, /data, { readOnly: false }); await sandbox.exec(ls /data); await sandbox.unmountBucket(/data);env.DATA_BUCKET来自 wrangler.jsonc 的r2_buckets绑定。两条限制只在生产可用本地开发环境没有 FUSE本地调试只能 mock挂载是沙箱级作用域对该沙箱内所有会话可见不是单会话私有。readOnly: false时写入会回写 R2挂载 → 处理 → 结果落盘可以闭环。生产加固重试、成本、安全为什么第一次请求偶尔会 500容器首次供给、或刚从休眠醒来时操作会撞上CONTAINER_NOT_READY。官方姿势是等 2 秒再试最多 3 次async function execWithRetry(sandbox, cmd) { for (let i 0; i 3; i) { try { return await sandbox.exec(cmd); } catch (e) { if (e.code CONTAINER_NOT_READY) { await new Promise(r setTimeout(r, 2000)); continue; } throw e; } } }还有一个易混点命令失败不会抛异常。exec只返回success: false需要你自查exitCode与stderr。SDK 层异常带error.code另外两个常见码是FILE_NOT_FOUND路径不存在与TIMEOUT操作超时可调超时或拆任务。如何把账单控制住容器默认空闲超时sleepAfter缺省10m可选5m、1h、2dkeepAlive: false即自动休眠——这是成本友好的默认姿势。休眠沙箱在下次请求时自动唤醒冷启动大约 2–3 秒。⚠️ 给关键沙箱设keepAlive: true后容器永不休眠用完必须自己destroy()否则会一直运行、一直计费。放进finallyconst sandbox getSandbox(env.Sandbox, temp, { keepAlive: true }); try { const r await sandbox.exec(node render.js); return Response.json(r); } finally { await sandbox.destroy(); }destroy()是清场级操作文件、进程、会话、网络连接、已暴露端口全部删除。成本上还有两条复用 ID 而不是新建getSandbox(env.Sandbox,user-${userId})每次进来都是同一间房用Date.now()现造 ID 等于每次来访都拆房重建又慢又贵。预热关键沙箱配 cron 触发器定期跑一条无害命令避免房间被关灯。{ triggers: { crons: [*/5 * * * *] } }export default { async scheduled(event: ScheduledEvent, env: Env) { const sandbox getSandbox(env.Sandbox, main); await sandbox.exec(echo keepalive); } };供给超时也可调containerTimeouts里instanceGetTimeoutMS默认 30000portReadyTimeoutMS默认 90000两个环境变量SANDBOX_INSTANCE_TIMEOUT_MS、SANDBOX_PORT_TIMEOUT_MS可以整体覆盖。如何防止用户代码逃逸 shell、密钥进代码exec接收 shell 命令字符串拼接用户输入是注入温床// ❌ 别这么写用户代码直接进 shell await sandbox.exec(python3 -c ${userCode}); // ✅ 正确姿势先落盘再执行文件 await sandbox.writeFile(/workspace/job.py, userCode); await sandbox.exec(python3 /workspace/job.py);密钥同理绝不硬编码。用wrangler secret put存进平台从env读出经exec的env选项注入命令进程const token env.GITHUB_TOKEN; // 来自 wrangler secret await sandbox.exec(git fetch origin main, { env: { GIT_TOKEN: token } });随手套件CLI、日志与资源限制日常操作离不开五条命令wrangler dev本地调试、wrangler deploy上线、wrangler tail盯日志、wrangler containers list看容器状态、wrangler secret put KEY写密钥完整字段与环境变量在 configuration.md 里。日志行为由环境变量控制SANDBOX_LOG_LEVELdebug | info | warn | error默认info与SANDBOX_LOG_FORMATjson或pretty默认json。本地调试配debugpretty好读生产配info/warnjson方便采集。最后把所有场景收成一张表方便回查场景推荐 API关键坑一次性命令sandbox.exec失败返回success: false而非抛错要查exitCode读写文件readFile/writeFile仅/workspace持久/tmp易失常驻服务startProcesswaitForPort必须等端口就绪再暴露公开预览地址exposePort令牌每次调用轮换.workers.dev不支持WebSocket 代理wsConnect先判Upgrade: websocket头多租户工位createSession/getSession会话共享沙箱级的 Bucket 挂载AI 代码上下文createCodeContextrunCode容器重启后变量清空挂载 R2 存储mountBucketwrangler dev不可用控制成本sleepAfterdestroykeepAlive: true必须配destroy()硬约束速查proxyToSandbox(request, env)必须是 fetch handler 的第一句相同 ID 复用同一沙箱禁止用时间戳现造 ID持久文件只放/workspacenormalizeId保持一致需要预览 URL 时设true预览 URL 依赖自定义域名 通配符 DNS.workers.dev不支持keepAlive: true必须与finally里的destroy()配对撞CONTAINER_NOT_READY等 2 秒重试最多 3 次用户输入不拼进 shell 命令先writeFile再执行文件密钥走wrangler secret经exec的env注入R2 挂载仅限生产本地调试 Dockerfile 必须写EXPOSE【免费下载链接】skillsSkills Catalog for Codex项目地址: https://gitcode.com/GitHub_Trending/skills4/skills创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考