Chapter 26
Docker demo — Self-Hosted Sandboxes
Docker demo — Self-Hosted Sandboxes
The host runs ant beta:worker poll directly; per claimed work item its
--on-work script (on-work.sh) docker runs a per-session container whose
entrypoint is ant beta:worker run. Each container gets a /workspace (the
agent's working tree; skills download here) backed by a per-session Docker
volume so the tree and skills survive across containers for one session.
This is the no-cloud variant of ../cf/ (which runs the same
ant beta:worker run entrypoint, but in Cloudflare Containers): same CLI, same
env contract, just plain Docker on a host you control.
Dockerfile— the per-session image: pinnedantCLI,WORKDIR /workspace,ENTRYPOINT ["ant","beta:worker","run", …]. The CLI owns heartbeat, backlog reconcile, SSE, thebash/read/write/edit/glob/greptoolset, and the work-item force-stop on exit.on-work.sh— invoked by the poller per work item. Reads theANTHROPIC_{WORK_ID,ENVIRONMENT_ID,SESSION_ID,ENVIRONMENT_KEY}the poller sets, drains the work JSON on stdin, andexecs a--rmper-session container in the foreground (idempotent: a duplicate item for a live session is a no-op). It blocks until the container exits — it must, becauseant beta:worker pollposts a stop on the work item the moment the--on-workscript returns (no CLI opt-out). A detacheddocker run -dwould let the poller stop the work before the container could claim it, so thebashtool call would never run.start.sh— builds the image and execs the host poller with--on-work on-work.sh.
Idle policy is the SDK default: each container exits 60s after
session.status_idle with stop_reason: end_turn; any other event resets the
clock. --rm then removes it; the per-session volume persists for the next
message in that session.
No org API key anywhere: the only credential is the environment key. The
poller uses it to claim work; it's passed into each container as
ANTHROPIC_ENVIRONMENT_KEY (the per-session calls — event stream, lease
heartbeat, force-stop) and as ANTHROPIC_AUTH_TOKEN (the CLI's
skill-download client resolves only ANTHROPIC_API_KEY /
ANTHROPIC_AUTH_TOKEN, not ANTHROPIC_ENVIRONMENT_KEY — without it skills
silently fail to download).
Querying MongoDB from the sandbox
This image bundles python3 + pymongo, and on-work.sh forwards an optional MONGO_URI
into each per-session container, so the agent can query MongoDB straight from its bash tool:
python3 -c 'import os; from pymongo import MongoClient; \
c = MongoClient(os.environ["MONGO_URI"]); \
print(list(c["mydb"]["mycoll"].find({}, {"_id": 0}).limit(5)))'Set it host-side before ./start.sh (it flows poller → on-work.sh → container):
export MONGO_URI="mongodb+srv://<user>:<password>@<cluster>/"MONGO_URI is your secret, not Anthropic's. Because this is a container you build and run,
it's a normal env var that never reaches the control plane or the session event history — the
self-hosted advantage. (A cloud sandbox has no env-var or vault channel for a database secret,
so there you keep the credential host-side behind a custom tool instead. See the
MongoDB-on-CMA landing page and the
cookbook, whose Section 1
walks all three connection paths.)
The agent's bash runs inside this container, so it can read MONGO_URI — fine when you trust
the task. For least privilege, swap the built-in toolset for your own worker tool that exposes a
narrow mongo_query(...) instead of the raw URI. Prefer the MongoDB shell? Add mongosh to the
image (MongoDB's apt repo) and call mongosh "$MONGO_URI" --eval '…'.
Prerequisites
-
Docker
-
anton the host's PATH, the same build pinned inDockerfile(ARG ANT_VERSION). Install it:shVERSION=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
Run
export ANTHROPIC_ENVIRONMENT_ID=env_...
export ANTHROPIC_ENVIRONMENT_KEY=sk-ant-oat...
./start.shThen create a session pointing at that ANTHROPIC_ENVIRONMENT_ID and send it a
message. You should see, in order:
[start] polling env=env_... base=https://api.anthropic.com
[on-work] session=sesn_... work=work_... container=... (started)Container logs (docker logs cma-sesn_...) show the ant beta:worker run
JSON: downloaded skill …, executing tool …, etc. The container removes
itself after it idles out; the cma-ws-<session_id> volume remains for that
session's next message (docker volume rm to discard).
Unlike the webhook-driven demos (Modal/Daytona/Vercel/Cloudflare), there is no
webhook here — ant beta:worker poll long-polls the environment directly, so
nothing needs to be exposed to the internet.
