Run coding agents inside isolated Docker containers with a single command and a single shared workspace. Build locally, point at a repo, and let the agent work with full freedom inside the container instead of on your host machine.
The primary user-facing interface is the installable adr CLI. Install it once
with install.sh, then use adr build, adr run, adr status, and related
commands from any directory.
For multi-agent workflows, the companion adr-tmux CLI manages persistent
coding agent sessions via tmux — start multiple agents side-by-side, inject
prompts programmatically, and monitor session health.
For agents working in this repo, install the adr-master skill to get
comprehensive CLI reference documentation: build, run, and manage isolated
coding agent containers, plus adr-tmux multi-agent orchestration. Install it
directly from the repo:
bash skills/adr-master/install.shSee skills/adr-master/README.md for full details.
If you are a coding agent working in this repository, load the assistant skill to get full context on the project, all CLI scripts, and the spec/task workflow:
Load the file in <this-repo-root>/skills/agent-docker-runner-assistant/SKILL.md and help me work with this repository
Point your agent at that file (e.g. pass it as a skill, add it to context, or read it directly) and it will have everything needed to build, run, and contribute to this project.
- Docker installed and running.
- The agent image must be built first (see Building Images).
- No other dependencies — bash is all you need on the host.
For contributors using the spec workflow, additional tools such as
gitandyqare used by some helper scripts. They are not required just to run an agent container withadr run.
When using external coding agents (pi, opencode, claude, codex), the containers must have network access to connect to their respective APIs. This is required because these agents connect to external AI services to generate code and process prompts.
- pi — requires internet access to connect to the pi coding agent API (or your configured provider such as Ollama, LM Studio, OpenRouter, etc.)
- opencode — requires internet access to connect to the OpenCode API or your configured provider
- claude — requires internet access to connect to Anthropic's API
- codex — requires internet access to connect to OpenAI's API
If your environment restricts network access (e.g., corporate firewalls, air-gapped systems), you will need to configure the agents to use local models instead, or ensure the container has appropriate network connectivity.
Install the adr CLI into your local user account:
./install.shThis installs the adr and adr-tmux CLIs to ~/.local/bin and ~/.local/share :
The install is safe to re-run. Existing user config in ~/.config/adr/config
and any project .adr files are preserved.
After installation:
adr --help
adr status
adr-tmux helpIf ~/.local/bin is not on your PATH, install.sh prints a reminder.
Remove the installed CLI and bundled runtime files. Run with sudo to bypass
permission issues on systems where ~/.local/bin is not writable by your user:
./uninstall.shOr skip the confirmation prompt:
./uninstall.sh --yesUninstall does not remove:
~/.config/adr/config~/.config/adr-tmux/- project
.adrfiles - built Docker images such as
coding-agent/pi:latest
adr-tmux manages persistent coding agent sessions via tmux. Start multiple
agents side-by-side in split panes, inject prompts programmatically, and
monitor session health with stall detection.
Relationship to adr:
adrmanages containerized agent execution (adr run <agent>)adr-tmuxorchestrates persistent multi-agent workflows via tmux sessions- Both are installed together via
install.sh
- tmux 3.0+ — required for pane management and format strings
- adr CLI — must be installed and functional (
adr --help) - bash 4.0+
# 1. Build agent images (if not already built)
adr build pi
adr build codex
# 2. Start a session with two agents
adr-tmux start refactor --agent pi:main --agent codex --workspace ~/projects/myapp
# 3. Attach to the session
adr-tmux inject refactor
# 4. Send a prompt to a specific agent (from another terminal)
adr-tmux send refactor --agent main "Refactor the auth module to use dependency injection"
# 5. Check session status
adr-tmux status
# 6. Monitor for stalled agents
adr-tmux watch refactor
# 7. Stop the session when done
adr-tmux stop refactoradr-tmux start <session-name> --agent <type>[:<name>]... --workspace <path>Creates a detached tmux session with one pane per agent. Each pane runs
adr run <agent> -w $WORKSPACE.
# Single agent (auto-generates name "pi-1")
adr-tmux start review --agent pi --workspace ~/projects/myapp
# Multiple agents with explicit names
adr-tmux start refactor --agent pi:main --agent codex --workspace ~/projects/myapp
# Multiple instances of the same agent
adr-tmux start review --agent pi:main --agent pi:backup --workspace ~/projects/myapp
# Auto-generate name with wildcard
adr-tmux start quick --agent "pi:*" --workspace ~/projects/myappSession structure:
tmux session: "adr-refactor"
├─ environment: WORKSPACE=/home/user/projects/myapp
└─ window 0: "agents"
├─ pane 0 (title="main") → adr run pi -w /workspace
└─ pane 1 (title="codex-1") → adr run codex -w /workspace
adr-tmux inject refactorAttaches the terminal to the tmux session. Standard tmux keybindings apply (Ctrl+b prefix). Detach with Ctrl+b d.
adr-tmux send refactor --agent main "Refactor the auth module"Sends a prompt to a specific agent pane by matching the pane title (instance name). Works from any terminal — no need to attach to the session.
adr-tmux status # All sessions
adr-tmux status refactor # Specific sessionOutput:
Sessions:
─────────
refactor [uptime: 12m 34s] [workspace: ~/projects/myapp]
├─ main [RUNNING]
└─ codex-1 [STALLED - last change: 45s ago]
adr-tmux watch refactor # Watch all agents
adr-tmux watch refactor --agent main # Watch specific agentLoops every 3 seconds, detects RUNNING/STALLED state changes, and shows recent output snippets. Exit with Ctrl+C.
adr-tmux stop refactorKills the tmux session and all associated panes. Agent containers exit via ADR cleanup.
All sessions are prefixed with adr- to identify them among other tmux
sessions. You provide the name without the prefix:
- User provides:
refactor-auth - Actual tmux session:
adr-refactor-auth
- Explicit:
--agent pi:main→ pane titled "main" - Auto-generated:
--agent pi→ generates "pi-1", next becomes "pi-2" - Wildcard:
--agent pi:*→ generates "pi-1" - Names must be unique within a session
The status and watch commands detect stalled agents by comparing pane
content snapshots over time. If the pane content doesn't change across
consecutive checks (2 rounds × 3s intervals), the agent is marked as STALLED.
| Agent | Description | Config dir |
|---|---|---|
pi |
pi coding agent | ~/.pi/ |
opencode |
opencode | ~/.config/opencode/ |
claude |
Claude Code by Anthropic | ~/.claude/ |
codex |
OpenAI Codex CLI | ~/.codex/ |
# 1. Install the CLI
./install.sh
# 2. Build the image
adr build pi
# 3. Set up ~/.pi/ (if you don't already have one)
cp -r config-examples/pi/ ~/.pi
# Edit ~/.pi/agent/settings.json — add your API key(s) / configure provider
# 4. Launch an interactive session in the current directory
adr run pi# 1. Install the CLI
./install.sh
# 2. Build the image
adr build opencode
# 3. Set up ~/.config/opencode/ (if you don't already have one)
mkdir -p ~/.config/opencode
cp config-examples/opencode/opencode.json.example ~/.config/opencode/opencode.json
# Edit ~/.config/opencode/opencode.json — add your API key(s) / configure provider
# 4. Launch an interactive session in the current directory
adr run opencode# 1. Install the CLI
./install.sh
# 2. Build the image
adr build claude
# 3. Set up ~/.claude/ (if you don't already have one)
mkdir -p ~/.claude
cp config-examples/claude/settings.json.example ~/.claude/settings.json
# Edit ~/.claude/settings.json — add your Anthropic API key under "env"
# 4. Set up ~/.claude.json — pre-approves the API key so Claude Code doesn't prompt
cp config-examples/claude/.claude.json.example ~/.claude.json
# Edit ~/.claude.json — replace the placeholder with your real key
# (must match the key in settings.json exactly)
# 5. Launch an interactive session in the current directory
adr run claude# 1. Install the CLI
./install.sh
# 2. Build the image
adr build codex
# 3. Set up ~/.codex/ if you use file-based Codex config
mkdir -p ~/.codex
cp config-examples/codex/config.toml.example ~/.codex/config.toml
# 4. Export your API key if needed
export OPENAI_API_KEY=your-key-here
# 5. Launch an interactive session in the current directory
adr run codexYour current directory is mounted as /workspace — the agent can read and write files there.
Already using these agents on the host? Your existing config at
~/.pi/,~/.config/opencode/,~/.claude/plus~/.claude.json, or~/.codex/is picked up automatically — just build the image and run.
Each agent reads its config from its native config home on the host. Use -c
to override with a different directory.
| Agent | Default config path |
|---|---|
pi |
~/.pi/ |
opencode |
~/.config/opencode/ |
claude |
~/.claude/ |
codex |
~/.codex/ |
~/.pi/
agent/
settings.json # Default model, theme, and other pi settings
models.json # Custom providers/models (Ollama, LM Studio, OpenRouter, …)
extensions/ # Pi extensions — persist across container recreations
agent/models.jsonandextensions/are optional. Onlyagent/settings.jsonis needed for basic use.
~/.config/opencode/
opencode.json # Provider config, model definitions, API keys
Claude Code uses two config files:
| File | Location | Purpose |
|---|---|---|
settings.json |
~/.claude/ |
Claude Code settings — API key, env vars |
.claude.json |
~/.claude.json |
Pre-approves the API key |
~/.claude/
settings.json # Claude Code settings — use the "env" block for your API key
~/.claude.json # Pre-approves the API key (`adr run` reads this automatically)
settings.json minimal example (see config-examples/claude/settings.json.example):
{
"env": {
"ANTHROPIC_API_KEY": "sk-ant-api03-..."
}
}~/.claude.json minimal example (see config-examples/claude/.claude.json.example):
{
"customApiKeyResponses": {
"approved": [
"sk-ant-api03-YOUR-FULL-API-KEY-HERE"
],
"rejected": []
}
}The approved list must contain the full API key — the same value set in
settings.json. This tells Claude Code the key has already been accepted and
prevents the interactive "Do you want to use this API key?" prompt that would
block container startup, especially in headless mode.
adr run reads ~/.claude.json by default. Use --config-file to point to a
different location.
The config directory is staged into a temporary world-readable location (to work around
--cap-drop ALL blocking reads from the root process). The entrypoint then copies it into
the agent's config home inside the container and drops privileges before launching the agent.
# Interactive TUI session with a specific workspace
adr run -w ~/projects/myapp pi
adr run -w ~/projects/myapp opencode
adr run -w ~/projects/myapp claude
# Headless: give the agent a one-shot task and exit (--prompt implies --headless)
adr run -w ~/projects/myapp \
--prompt "Write tests for all untested functions in src/" pi
adr run -w ~/projects/myapp \
--prompt "Write tests for all untested functions in src/" opencode
adr run -w ~/projects/myapp \
--prompt "Write tests for all untested functions in src/" claude
# Use a specific model (for pi, "provider/model" selects both)
adr run -w ~/projects/myapp --model anthropic/claude-sonnet-4 pi
adr run -w ~/projects/myapp --model anthropic/claude-sonnet-4 opencode
adr run -w ~/projects/myapp --model sonnet claude
# Use a pinned image version
adr run -w ~/projects/myapp --tag 1.2.3 claude
# Debug: drop into a shell inside the container
adr run -w ~/projects/myapp --shell claude
# Override config directory (e.g. for a separate project-specific config)
adr run -w ~/projects/myapp -c ~/my-custom-config claude
# Mount an additional host directory read-only (default when mode is omitted)
adr run -m /data/reference:/data claude
# Mount a directory read-write (e.g. for agent output)
adr run --mount /tmp/output:/output:rw pi
# Multiple mounts
adr run -m /data/reference:/data:ro -m /tmp/output:/output:rw claude
# Pass arbitrary parameters to the agent (everything after -- is forwarded)
adr run pi -- --my-parameter value --another-flag# Build latest
adr build pi
adr build opencode
adr build claude
# Build a specific version tag
adr build --tag 1.2.3 claude
# Force a clean rebuild
adr build --no-cache claudeImages are named coding-agent/<agent>:<tag> and stay local — nothing is pushed to a registry.
Copy config-examples/pi/agent/models.json.example to
~/.pi/agent/models.json, configure the provider you want
(Ollama, LM Studio, OpenRouter, …), and run as normal.
Pi hot-reloads models.json when you open /model — no container restart needed.
Copy config-examples/opencode/opencode.json.example to
~/.config/opencode/opencode.json and configure the providers and models
you want. Pass --model provider/model to select one at runtime.
Claude Code connects to Anthropic's API by default. Set ANTHROPIC_API_KEY in
~/.claude/settings.json under the "env" key, and add the same
key to the customApiKeyResponses.approved list in
~/.claude.json. Pass --model with a short alias
(sonnet, opus) or a full model name (claude-sonnet-4-6).
host.docker.internal is automatically configured for Linux (via
--add-host=host.docker.internal:host-gateway) so local LLM servers (Ollama,
LM Studio) are reachable without extra setup on any platform.
Usage: adr run [OPTIONS] [<agent>]
Arguments:
agent Agent to run. Supported: pi, opencode, claude, codex
Options:
-w, --workspace DIR Host directory mounted as /workspace inside the
container. Defaults to current working directory ($PWD).
-c, --config DIR Path to config directory for the agent.
Defaults to the agent's native config home:
pi: ~/.pi/
opencode: ~/.config/opencode/
claude: ~/.claude/
codex: ~/.codex/
Mounted read-only to a staging path and copied into
the agent's config directory at container startup.
Must exist on the host.
--config-file FILE Path to a .claude.json file copied to ~/.claude.json
inside the container. Pre-approves the API key so
Claude Code does not prompt on startup.
Defaults to ~/.claude.json on the host.
Only accepted for the claude agent.
--prompt TEXT Prompt to pass to the agent. Implies --headless.
--headless Run in headless / non-interactive mode (no TUI).
Requires --prompt. Implied by --prompt.
--shell Drop into bash instead of running the agent (debug).
--tag TAG Docker image tag to use. Default: latest.
--model MODEL Model to use.
For pi, use "provider/model" to also select the
provider (e.g. anthropic/claude-sonnet-4). The part
before the first "/" is the provider; everything after
is the model ID (e.g. evo/qwen/qwen3-coder-next).
opencode: "provider/model" format (e.g. anthropic/claude-sonnet-4).
claude: alias (e.g. sonnet, opus) or full name (e.g. claude-sonnet-4-6).
codex: model name understood by the Codex CLI.
-e, --env KEY=VALUE Set an environment variable inside the container.
May be specified multiple times.
--env-file FILE Load environment variables from a file (.env format).
-m, --mount HOST:CONTAINER[:ro|rw]
Mount a host directory into the container.
Mode is optional and defaults to ro (read-only).
May be specified multiple times; mounts accumulate
in the order they are declared.
Host path must exist and be a directory.
Example: -m /data/reference:/data
Example: --mount /tmp/output:/output:rw
-h, --help Show this help text.
Additional directory mounts can be declared in ~/.config/adr/config (global)
or in a project .adr file so they apply automatically without repeating flags
on every invocation.
# ~/.config/adr/config or .adr
ADR_MOUNTS=/host/reference:/data:ro /host/output:/output:rw
- Value is a space-separated list of
HOST:CONTAINER[:ro|rw]entries. - Same syntax and validation rules as
--mount. - Config-file mounts are applied first;
--mountflags from the CLI append afterward.
Usage: adr build [OPTIONS] [<agent>]
Arguments:
agent Agent to build. Supported: pi, opencode, claude, codex
Options:
--tag TAG Docker image tag to apply. Default: latest.
Example: --tag 1.2.3 -> builds coding-agent/claude:1.2.3
--no-cache Pass --no-cache to docker build.
-h, --help Show this help text.
Usage: adr fix-owner [OPTIONS] [<directory>]
Arguments:
directory Target directory (defaults to current directory)
Options:
-h, --help Show this help text.
Changes ownership of all files in a directory recursively to the current user.
This command is useful on Linux when the container runs as UID 1000 and creates
files that are owned by that UID on the host. If your host user has a different
UID, use this command to fix ownership.
> **Requires passwordless sudo** — the command uses `sudo chown` to change file
> ownership, which requires your user to have passwordless sudo privileges.
The container runs as the node user (UID 1000). Files the agent creates in /workspace
will be owned by UID 1000 on the host. If your host user has a different UID, make the
workspace directory group/world-writable:
chmod o+w ~/projects/myappAlternatively, use the adr fix-owner command to change ownership of all files in a directory
recursively to your current user:
# Fix ownership in current directory
adr fix-owner
# Or specify a target directory
adr fix-owner /path/to/workspaceRequires passwordless sudo —
adr fix-ownerusessudo chownunder the hood.
Docker Desktop's VirtioFS layer remaps file ownership transparently.
Each agent runs in an isolated container with minimal privileges. It can only read and write
files inside /workspace on the host — nothing else is accessible. Secrets (API keys, config)
are staged from your config directory and are never baked into Docker image layers.
| Control | Value |
|---|---|
| Network | Full outbound access (agent needs to call AI APIs) |
| Filesystem | /workspace is writable by default; additional mounts via --mount/ADR_MOUNTS can expose further host paths (read-only or read-write as configured) |
| Capabilities | --cap-drop ALL with --cap-add SETUID,SETGID for privilege dropping |
| Privilege escalation | --security-opt no-new-privileges |
| Container filesystem (non-workspace) | Writable by node user (UID 1000, not root) — ephemeral, discarded on exit |
| Secrets | Staged from host config directory, copied to container at startup |
| Claude permissions | --dangerously-skip-permissions is always passed — the container is the sandbox |
Because --cap-drop ALL removes all capabilities including CAP_DAC_OVERRIDE, the root
process cannot read files from a read-only mount if they're owned by a different user.
The solution: stage config to a temporary world-readable location, then copy it as the
non-root node user (who owns the agent's config home inside the container).
Agent Docker Runner is built with the spec framework that lives in this repository. That framework is a development tool for contributors, not a runtime dependency of the runner itself.
If you are changing behavior, adding an agent, or implementing a feature, this is the preferred path:
- Define or refine the feature spec.
- Review the spec before writing code.
- Generate a task list from the reviewed spec.
- Implement against that task list in a worktree.
Typical commands:
# Inspect available workflow commands
bin/spec --help
# Create or guide a new feature workflow
bin/spec new-feature
# Brainstorm a specific feature
bin/spec feature-brainstorm timeout-enforcement
# Review an existing feature spec
bin/spec review timeout-enforcement
# Generate implementation tasks
bin/spec generate-tasks timeout-enforcement
# Launch the implementation loop
bin/spec implement timeout-enforcement --agent codexWhy this is worth using:
- Specs make behavior explicit before code starts drifting
- Reviews happen at the behavior level, where changes are cheaper
- Task generation reduces missed edge cases and forgotten tests
- Worktrees keep feature work isolated and easier to reason about
If you are only trying to run an agent in a container, you can ignore this
entire section and just use adr.
- Create
agents/<name>/Dockerfile— install the agent, create a non-root user, set WORKDIR to/workspace, set ENTRYPOINT. - Create
agents/<name>/entrypoint.sh— translateAGENT_HEADLESS,AGENT_PROMPT,AGENT_SHELL,AGENT_PROVIDER,AGENT_MODELenv vars into the agent's own CLI flags. - Add the agent name to
KNOWN_AGENTSin the ADR CLI runtime undercli/runtime.sh. - Document the config layout in
README.mdand add an example underconfig-examples/<name>/.
adr run is agent-agnostic by design once the entrypoint contract is in place.