Build an agent pool from a devcontainer¶
Agents run coding-agent sessions in their own container, so the container needs the project’s toolchain (ADR-0025: Agent Fleet Runs Sessions as Processes). Build it from the project’s devcontainer: the ballet-agent devcontainer Feature adds the agent, Claude Code and an unprivileged session user to whatever the devcontainer already installs. Every container of that image is an agent of the image’s pool; Ballet sends a project’s runs to the agents of its pool.
1. Add the Feature¶
The Feature lives in .devcontainer/ballet-agent. Until it is
published to a registry, bundle the agent into it and copy it next to the
project’s devcontainer.json (devcontainers only use local Features
inside the .devcontainer folder):
make feature # builds the agent for amd64 and arm64 into .devcontainer/ballet-agent/bin
cp -r .devcontainer/ballet-agent /path/to/project/.devcontainer/
Then add it to the project’s devcontainer.json — or to a second
configuration such as .devcontainer/pool/devcontainer.json, so the
developers’ own configuration stays as it is:
{
"build": { "dockerfile": "../Dockerfile", "context": ".." },
"features": {
"../ballet-agent": { "pool": "web" }
}
}
Options:
poolThe pool label (lowercase letters, digits,
.,_,-). Empty: the agents take any project’s runs that names no pool.sessionUserThe OS user sessions run as, created if missing (default
ballet). Sessions then cannot read the agent’s token. Empty: sessions run as root — only when the image keeps its tools where other users cannot reach them, like Ballet’s own (Run Ballet on Ballet).agentUrlWhere to download the agent when the Feature does not bundle it (
${ARCH}:amd64orarm64).installClaudeInstall Claude Code (default
true; skipped whenclaudeis in/usr/local/binor/usr/binalready).installOpencodeInstall opencode (default
false; skipped when it is installed), for projects whose stages run on it.
The Feature writes /etc/ballet/agent.toml (the pool label and the
session user; BALLET_AGENT_CONFIG points at it) — BALLET_AGENT_*
variables still override it (Sessions).
2. Build the image¶
With the devcontainer CLI (npm install -g @devcontainers/cli):
devcontainer build --workspace-folder /path/to/project \
--config /path/to/project/.devcontainer/pool/devcontainer.json --image-name acme-web-pool
.devcontainer/agent-pool-example/devcontainer.json is a minimal
example; make feature-test builds it and checks the result, as CI does.
3. Run the agents¶
Run the image with the agent as its command, Core’s URL and the agent
token Core writes (data/service-tokens/agent.token):
/usr/local/bin/ballet-agentThe command. (
devcontainer upstarts it through the Feature’s entrypoint whenBALLET_AGENT_CORE_URLis set.)BALLET_AGENT_CORE_URLCore’s base URL as reached from the container.
BALLET_AGENT_CORE_TOKEN_FILEThe agent token, mounted read-only.
With the compose installation (Install Ballet on one host):
BALLET_AGENT_IMAGE=acme-web-pool docker compose -f deploy/compose.yaml up -d agent
On Kubernetes, a Deployment of N replicas of the image, with the token in
a Secret, is the whole pool; scale it by changing N. Sessions reach the
LLM gateway, Knowledge and Core’s tracker through the URLs Core hands
out (agents.gateway_url, agents.knowledge_mcp_url,
agents.tracker_mcp_url, Configuration).
4. Send the project’s runs to the pool¶
In the project’s settings, set Agent pool to the pool label
(pool in Execution settings). Its runs then go only to
agents labelled pool=<label>; while none is connected, they wait in
the queue. Projects without a pool use any agent.