claude-cookbooks 实战:用纯 Docker 自托管 Sandbox 跑通 Claude Managed Agents 完整轮询链路
导读
本文以 managed_agents/self_hosted_sandboxes/docker/ 目录下的参考实现为主线,完整讲解如何在你自己控制的主机上,用纯 Docker 为 Claude Managed Agents(CMA)会话提供自托管执行沙箱:宿主机运行 ant beta:worker poll 持续认领工作项,每个会话通过 --on-work 脚本拉起一个隔离的 per-session 容器来执行 ant beta:worker run。读完本文,你将掌握这一无云方案的架构分工、三份关键脚本的实现细节、环境变量契约、空闲回收策略,以及如何在沙箱内直接用 bash 工具查询 MongoDB,并能照搬文中命令在一台仅装有 Docker 的机器上完整跑通。
一图看懂运行模型:宿主机轮询 + 每会话容器
docker/ 变体的核心思路非常直观:把「轮询认领」和「真正干活」拆到两个进程层级。
- **宿主机(host)**直接运行
ant beta:worker poll。它是控制面侧的常驻轮询器,负责长轮询当前自托管环境(environment)下分配给它的工作项(work item),并在每认领到一个工作项时调用--on-work指定的脚本(即on-work.sh)。 - 每个被认领的工作项由
on-work.sh通过docker run拉起一个 per-session 容器,容器入口是镜像内置的ant beta:worker run。工具执行(bash/read/write/edit/glob/grep)、租约心跳、SSE 事件流、工作项退出时的强制停止等,全部由容器内的 CLI 负责。 - 每个容器都会挂载一个
/workspace,即 Agent 的工作树(技能skills也下载到这里)。/workspace由每会话独立的 Docker volume 提供支撑,因此同一会话跨多个容器仍然保留工作树与已下载的技能。
这份实现是 managed_agents/self_hosted_sandboxes/cf/(Cloudflare Containers 变体,运行同一个 ant beta:worker run 入口)的无云孪生版本:两者共用同一套 CLI、同一套环境变量契约,区别只在于前者跑在 Cloudflare Containers 上,而这里跑在你自己掌控的主机 Docker 上。整个 docker/ 目录只有三个文件,职责清晰:
| 文件 | 作用 |
|---|---|
managed_agents/self_hosted_sandboxes/docker/Dockerfile |
定义 per-session 镜像:内置固定版本的 ant CLI、WORKDIR /workspace、ENTRYPOINT ["ant","beta:worker","run",…] |
managed_agents/self_hosted_sandboxes/docker/on-work.sh |
被轮询器按工作项调用;读取环境变量、吞掉 stdin 上的 work JSON,然后前台拉起 --rm 的 per-session 容器 |
managed_agents/self_hosted_sandboxes/docker/start.sh |
宿主侧启动脚本:先构建镜像,再以 --on-work on-work.sh 方式 exec 宿主轮询器 |
Dockerfile:构建一个「自带工具链」的 per-session 镜像
managed_agents/self_hosted_sandboxes/docker/Dockerfile 以 debian:12-slim 为基础,通过 ARG 将 ant CLI 版本固化在镜像层里:
FROM debian:12-slim
ARG ANT_VERSION=1.10.0
ARG TARGETOS=linux
ARG TARGETARCH=amd64
几点值得注意的设计:
- 版本锁定靠
ARG ANT_VERSION。镜像从anthropics/anthropic-cli的 GitHub Releases 下载ant_${ANT_VERSION}_${TARGETOS}_${TARGETARCH}.tar.gz并解压到/usr/local/bin。宿主机上安装的ant必须与Dockerfile中这个ANT_VERSION保持一致(即同为 1.10.0),否则宿主与容器两端 CLI 契约可能错位,这是start.sh检查ant存在后仍建议你核对ANT_VERSION的原因。 - 预装 MongoDB 客户端:
python3、python3-pymongo让 Agent 能直接从bash工具里查询 MongoDB;python3-dnspython也是必装的——因为 Atlas 的连接串几乎都是mongodb+srv://格式,缺少它pymongo会直接抛"The dnspython module must be installed to use mongodb+srv:// URIs"(该说明直接写在Dockerfile注释中)。 - 工作目录约定:
WORKDIR /workspace把 Agent 的工作树锚定在容器内/workspace,文件类工具的作用范围也以此为界。 - 入口即契约:容器入口固定为
ENTRYPOINT ["ant", "beta:worker", "run", "--workdir", "/workspace", "--unrestricted-paths", "--max-idle", "60s", "--log-format", "json"]
其中 --max-idle 60s 定义空闲回收策略,--log-format json 让 docker logs 输出的运行日志是结构化 JSON,便于检索工具执行与技能下载记录。
on-work.sh:为什么必须「前台阻塞」而不是 docker run -d
managed_agents/self_hosted_sandboxes/docker/on-work.sh 是整个方案里最容易写错、也最有讲究的一个脚本。它的完整逻辑如下:
#!/usr/bin/env bash
set -euo pipefail
cat >/dev/null # drain the work JSON on stdin
: "${ANTHROPIC_SESSION_ID:?on-work: ANTHROPIC_SESSION_ID not set by poller}"
: "${ANTHROPIC_ENVIRONMENT_ID:?on-work: ANTHROPIC_ENVIRONMENT_ID not set by poller}"
: "${ANTHROPIC_WORK_ID:?on-work: ANTHROPIC_WORK_ID not set by poller}"
: "${ANTHROPIC_ENVIRONMENT_KEY:?on-work: ANTHROPIC_ENVIRONMENT_KEY not set by poller}"
IMAGE="${CMA_IMAGE:-cma-self-hosted-sandbox-docker}"
NAME="cma-${ANTHROPIC_SESSION_ID}" # session ids are docker-name-safe
VOLUME="cma-ws-${ANTHROPIC_SESSION_ID}"
if [ -n "$(docker ps -q --filter "name=^${NAME}$" 2>/dev/null)" ]; then
echo "[on-work] session=${ANTHROPIC_SESSION_ID} already has a live container; skipping" >&2
exit 0
fi
docker rm -f "$NAME" >/dev/null 2>&1 || true # clear any exited leftover
echo "[on-work] session=${ANTHROPIC_SESSION_ID} work=${ANTHROPIC_WORK_ID} (running container in foreground)" >&2
exec docker run --rm --name "$NAME" \
-v "${VOLUME}:/workspace" \
-e "ANTHROPIC_BASE_URL=${ANTHROPIC_BASE_URL:-https://api.anthropic.com}" \
-e "ANTHROPIC_ENVIRONMENT_KEY=${ANTHROPIC_ENVIRONMENT_KEY}" \
-e "ANTHROPIC_AUTH_TOKEN=${ANTHROPIC_ENVIRONMENT_KEY}" \
-e "ANTHROPIC_SESSION_ID=${ANTHROPIC_SESSION_ID}" \
-e "ANTHROPIC_ENVIRONMENT_ID=${ANTHROPIC_ENVIRONMENT_ID}" \
-e "ANTHROPIC_WORK_ID=${ANTHROPIC_WORK_ID}" \
-e "MONGO_URI=${MONGO_URI:-}" \
"$IMAGE"
它被轮询器按工作项调用的前后契约如下:
- 轮询器把
ANTHROPIC_{WORK_ID,ENVIRONMENT_ID,SESSION_ID,ENVIRONMENT_KEY}通过环境变量传入,把原始 work JSON 放在 stdin 上(脚本第一行cat >/dev/null将其排空;本实现用不到,只是保持契约兼容)。 ANTHROPIC_BASE_URL从轮询器进程继承,未设置时回退到https://api.anthropic.com。
幂等保护与清理
会话 id 本身是 docker 命名安全的,因此脚本用 cma-${ANTHROPIC_SESSION_ID} 作为容器名、cma-ws-${ANTHROPIC_SESSION_ID} 作为 volume 名。首先检查同名单例容器是否仍在运行:
- 仍存活 → 说明该会话已被正在跑的
ant beta:worker run占有,重复工作项直接跳过(no-op),工作项租约自然过期,等待容器空闲退出后由下一次轮询重新认领; - 已退出残留 →
docker rm -f清掉,再重新拉起。
前台阻塞是硬约束,不是风格偏好
脚本最后用 exec docker run(不带 -d)前台执行,并阻塞到容器退出。这一点在 on-work.sh 的注释与目录 README 中都给出了明确理由:ant beta:worker poll 在 --on-work 脚本返回的那一刻就会对工作项发出停止指令(CLI 不提供关闭该行为的参数)。假如改用 docker run -d 让脚本立即返回,轮询器会抢在刚拉起的容器认领工作之前就把它停掉(表现为「heartbeat reports shutdown, state stopped」),bash 工具调用永远不会真正执行。因此 on-work.sh 必须替容器「续命」到 ant beta:worker run 自然结束。
--rm 保证容器空闲退出后被 Docker 自动移除;而挂载到 /workspace 的 per-session volume 会保留下来,供该会话的下一条消息使用。
双环境变量的玄机:为什么还要设 ANTHROPIC_AUTH_TOKEN
容器拿到的环境变量里,ANTHROPIC_ENVIRONMENT_KEY 与 ANTHROPIC_AUTH_TOKEN 被设成同一个值。原因在目录 README 与脚本注释中都有说明:
- 轮询器用它认领工作,会话级调用(事件流、租约心跳、强制停止)都以
ANTHROPIC_ENVIRONMENT_KEY为凭据,因此它必须原样传入容器; - 但 CLI 的技能下载客户端只解析
ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKEN,并不认识ANTHROPIC_ENVIRONMENT_KEY。若不额外设置ANTHROPIC_AUTH_TOKEN,技能会静默下载失败,且不容易察觉。
start.sh:一次构建、永久轮询的宿主启动器
managed_agents/self_hosted_sandboxes/docker/start.sh 负责宿主侧的全部装配:
set -euo pipefail
cd "$(dirname "$0")"
: "${ANTHROPIC_ENVIRONMENT_ID:?set ANTHROPIC_ENVIRONMENT_ID (env_...)}"
: "${ANTHROPIC_ENVIRONMENT_KEY:?set ANTHROPIC_ENVIRONMENT_KEY (sk-ant-oat...)}"
export ANTHROPIC_BASE_URL="${ANTHROPIC_BASE_URL:-https://api.anthropic.com}"
command -v docker >/dev/null || { echo "docker not found on PATH" >&2; exit 1; }
command -v ant >/dev/null || { ... }
IMAGE="${CMA_IMAGE:-cma-self-hosted-sandbox-docker}"
docker build -t "$IMAGE" .
exec env CMA_IMAGE="$IMAGE" MONGO_URI="${MONGO_URI:-}" \
ant beta:worker poll \
--on-work "$PWD/on-work.sh" \
--workdir /tmp \
--log-format json
它的职责与细节包括:
- 前置校验:要求
ANTHROPIC_ENVIRONMENT_ID(形如env_...)与ANTHROPIC_ENVIRONMENT_KEY(形如sk-ant-oat...)必须显式设置;校验宿主机存在docker与ant两个命令。 - 构建镜像:镜像名可用
CMA_IMAGE覆盖,默认cma-self-hosted-sandbox-docker。 - 启动轮询器:用
exec把自身替换为ant beta:worker poll,通过--on-work把每个工作项委托给on-work.sh。轮询进程本身不执行任何工具,因此它的--workdir指向一次性目录/tmp即可。CMA_IMAGE与MONGO_URI以环境变量的形式被继承进 on-work.sh 再到容器。轮询器收到 SIGTERM/SIGINT 会干净退出,并级联到正在运行的外层脚本。
需要提醒的是,关于 ant beta:worker poll 与 ant beta:worker run 的完整参数语义(环境变量契约、--on-work 的自定义外部脚本模式、--max-idle 等),可进一步参考 managed_agents/self_hosted_sandboxes/docs/usage-guide.md 中的 Flag 表格与示例。
空闲回收策略:60 秒后自动退出
空闲策略走的是 SDK/CLI 默认语义:每个容器在 session.status_idle 且 stop_reason 为 end_turn 之后 60 秒退出(对应镜像入口的 --max-idle 60s);期间任何其他事件都会重置计时器。容器退出后:
--rm自动移除该容器;- 名为
cma-ws-<session_id>的 per-session volume 继续保留,供该会话下一条消息复用(/workspace工作树与已下载技能不丢失); - 想要丢弃这份持久化状态,用
docker volume rm cma-ws-<session_id>手动删除即可。
安全模型:全程只有一枚环境密钥
这套实现贯穿始终的原则是:任何地方都不出现组织级 API key,唯一凭据就是环境密钥(environment key)。
- 宿主轮询器用它认领工作;
- 它被注入每个容器:既作为
ANTHROPIC_ENVIRONMENT_KEY(服务事件流、租约心跳、强制停止等会话级调用),又作为ANTHROPIC_AUTH_TOKEN(让技能下载客户端能鉴权,见前文)。
也就是说,即便某个会话容器被攻破,攻击者拿到的也只是受限到单一环境的密钥,而非组织级凭据。宿主与容器之间的所有通信(长轮询、事件流)都走 ANTHROPIC_BASE_URL,默认指向 https://api.anthropic.com,也支持通过环境变量指向代理/中转网关做网络边界控制。
从沙箱查询 MongoDB:把 MONGO_URI 送进容器
自托管沙箱的典型增值场景是让 Agent 直接操作你的数据库。该镜像打包了 python3 + pymongo,而 on-work.sh 会把可选的 MONGO_URI 转发进每个 per-session 容器,于是 Agent 可以纯粹通过 bash 工具完成查询:
python3 -c 'import os; from pymongo import MongoClient; \
c = MongoClient(os.environ["MONGO_URI"]); \
print(list(c["mydb"]["mycoll"].find({}, {"_id": 0}).limit(5)))'
环境变量的传递链路
MONGO_URI 的流动路径是宿主 → 轮询器 → on-work.sh → 容器。只需在运行 ./start.sh 之前在宿主侧导出:
export MONGO_URI="mongodb+srv://<user>:<password>@<cluster>/"
注意 start.sh 会把 MONGO_URI="${MONGO_URI:-}" 注入轮询器环境,on-work.sh 再以 -e "MONGO_URI=${MONGO_URI:-}" 转发;未设置时以空字符串透传,容器内读取为空,属于无害的 no-op。
为什么这是「自托管」的独特优势
MONGO_URI 是你自己的数据库密钥,不属于 Anthropic。因为这是你自己构建并运行的容器,它只是一个普通的进程环境变量,永远不会到达控制面,也不会进入会话事件历史——这是自托管方案相对云沙箱的核心收益之一。对比之下,云端沙箱没有可用的环境变量或 vault 通道来放置数据库密钥,通常只能把凭据留在宿主侧、包进一个自定义工具中暴露。三种连接路径的完整对照,参见 managed_agents/self_hosted_sandboxes/docker/README.md 引用的 MongoDB on Claude Managed Agents 着陆页 以及 CMA_with_mongodb_atlas.ipynb(其 Section 1 对全部三条连接路径均有演示)。
最小权限与替代方案
Agent 的 bash 运行在容器内,所以它能读取 MONGO_URI——在信任任务本身的前提下这是没问题的。若需要最小权限,两个可选方向:
- 换掉内置工具集:不暴露原始 URI,而是用你自己的 worker 工具暴露一个窄接口
mongo_query(...),对 Agent 隐藏连接串; - 使用
mongosh:更习惯 MongoDB Shell 的话,可在镜像中额外加入mongosh(MongoDB 官方 apt 源),然后在容器内调用mongosh "$MONGO_URI" --eval '…'。
前置条件:Docker 与版本对齐的 ant CLI
运行本方案需要两样东西:
- Docker(宿主机可用
docker命令即可); ant在宿主 PATH 上,且必须与Dockerfile中ARG ANT_VERSION锁定的构建一致(当前为 1.10.0)。
ant CLI 的官方安装片段如下(VERSION 必须与 Dockerfile 的 ANT_VERSION 保持一致):
VERSION=1.10.0 # must match ANT_VERSION in Dockerfile
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m | sed -e 's/x86_64/amd64/' -e 's/aarch64/arm64/')
curl -fsSL "https://github.com/anthropics/anthropic-cli/releases/download/v${VERSION}/ant_${VERSION}_${OS}_${ARCH}.tar.gz" \
| sudo tar -xz -C /usr/local/bin ant
ant --version
脚本会根据操作系统与 CPU 架构自动拼出对应的下载地址(x86_64 → amd64,aarch64 → arm64),解压后单个 ant 二进制落到 /usr/local/bin。
运行:两条命令看到容器被拉起
在完成「创建 self-hosted environment 并生成 environment key」的前置动作后(具体流程见 usage-guide),运行方式非常简短:
export ANTHROPIC_ENVIRONMENT_ID=env_...
export ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat...
./start.sh
随后创建一个指向该 ANTHROPIC_ENVIRONMENT_ID 的会话并向它发送一条消息。宿主终端应当按顺序看到两类日志:
[start] polling env=env_... base=https://api.anthropic.com
[on-work] session=sesn_... work=work_... container=... (started)
验证与观察手段
- 容器日志用
docker logs cma-sesn_...查看,里面是ant beta:worker run输出的 JSON(--log-format json),能看到类似downloaded skill …、executing tool …的记录; - 容器在空闲超时后自我移除;
cma-ws-<session_id>volume 保留给该会话下一条消息;确认不再需要时可docker volume rm丢弃。
无需暴露任何公网端口
与基于 webhook 驱动的演示(Modal / Daytona / Vercel / Cloudflare)不同,本方案没有任何 webhook:ant beta:worker poll 直接长轮询环境本身,因此宿主机不需要向公网暴露任何端口。这是它适合「受控主机 + 内网数据」场景的根本原因。
与仓库其他变体的关系
docker/ 只是 self-hosted sandbox 参考实现的一支。目录 README(managed_agents/self_hosted_sandboxes/README.md)给出了完整变体矩阵:docker/(本机 Docker)、cf/(Cloudflare Containers)、cf-worker/(无容器的 Cloudflare Workers + 进程内伪文件系统)、modal/、daytona/、vercel/。所有变体实现的是同一套契约:接收 session.status_run_started webhook → 排空环境工作队列以兜底错过的工作项 → 按工作项拉起一个以 SDK/CLI 工具运行器为核心的 per-session 沙箱。若想对比「同一入口、换一个计算供应商」的写法,docker/ 的无云对照物是 managed_agents/self_hosted_sandboxes/cf/README.md。
如果不需要容器级隔离、希望进一步理解 poll/run 两阶段 worker 在 SDK 层的 library 用法(.worker(...) 组合循环、beta_agent_toolset_20260401 工具集定制等),可继续阅读 managed_agents/self_hosted_sandboxes/docs/usage-guide.md。升级 SDK 版本时的迁移注意点则在 managed_agents/self_hosted_sandboxes/docs/upgrade-guide.md。
小结
把本文的内容浓缩成一张可直接照做的清单:一份锁定 CLI 版本的 Dockerfile 决定容器内能做什么(内置工具链 + MongoDB 客户端 + 60 秒空闲回收);一个前台阻塞的 on-work.sh 保证工作项不会被轮询器过早停掉,同时用双环境变量打通技能下载鉴权;一个只跑轮询不跑工具的 start.sh 负责装配与启动。三者组合,就得到了一套「零公网暴露、单密钥鉴权、每会话隔离、状态跨容器持久化」的自托管 CMA 沙箱,并且可以直接利用注入的 MONGO_URI 让 Agent 在 bash 里安全地查询你的数据库。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0627
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00