Skip to content

julweber/agent-docker-runner

Repository files navigation

Agent Docker Runner

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.sh

See skills/adr-master/README.md for full details.

For AI Coding Agents

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.


Prerequisites

  • 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 git and yq are used by some helper scripts. They are not required just to run an agent container with adr run.

Network Requirements

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.


Installation

Install the adr CLI into your local user account:

./install.sh

This 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 help

If ~/.local/bin is not on your PATH, install.sh prints a reminder.

Uninstallation

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.sh

Or skip the confirmation prompt:

./uninstall.sh --yes

Uninstall does not remove:

  • ~/.config/adr/config
  • ~/.config/adr-tmux/
  • project .adr files
  • built Docker images such as coding-agent/pi:latest

adr-tmux — Multi-Agent Tmux Controller

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:

  • adr manages containerized agent execution (adr run <agent>)
  • adr-tmux orchestrates persistent multi-agent workflows via tmux sessions
  • Both are installed together via install.sh

Prerequisites

  • tmux 3.0+ — required for pane management and format strings
  • adr CLI — must be installed and functional (adr --help)
  • bash 4.0+

Quick Start

# 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 refactor

Commands

adr-tmux start — Create a session with agent panes

adr-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/myapp

Session 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 — Attach to a running session

adr-tmux inject refactor

Attaches the terminal to the tmux session. Standard tmux keybindings apply (Ctrl+b prefix). Detach with Ctrl+b d.

adr-tmux send — Programmatic prompt injection

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 — Display session and agent states

adr-tmux status          # All sessions
adr-tmux status refactor # Specific session

Output:

Sessions:
─────────
refactor [uptime: 12m 34s] [workspace: ~/projects/myapp]
  ├─ main        [RUNNING]
  └─ codex-1     [STALLED - last change: 45s ago]

adr-tmux watch — Continuous monitoring

adr-tmux watch refactor              # Watch all agents
adr-tmux watch refactor --agent main # Watch specific agent

Loops every 3 seconds, detects RUNNING/STALLED state changes, and shows recent output snippets. Exit with Ctrl+C.

adr-tmux stop — Terminate a session

adr-tmux stop refactor

Kills the tmux session and all associated panes. Agent containers exit via ADR cleanup.

Session Naming

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

Instance Naming

  • 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

Stall Detection

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.


Supported Agents

Agent Description Config dir
pi pi coding agent ~/.pi/
opencode opencode ~/.config/opencode/
claude Claude Code by Anthropic ~/.claude/
codex OpenAI Codex CLI ~/.codex/

Quick Start

pi

# 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

opencode

# 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

claude

# 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

codex

# 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 codex

Your 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.


Config Directories

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 (~/.pi/)

~/.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.json and extensions/ are optional. Only agent/settings.json is needed for basic use.

opencode (~/.config/opencode/)

~/.config/opencode/
  opencode.json            # Provider config, model definitions, API keys

claude (~/.claude/ + ~/.claude.json)

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.

How Config Works

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.


Common Usage Examples

# 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

Building Images

# 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 claude

Images are named coding-agent/<agent>:<tag> and stay local — nothing is pushed to a registry.


Custom Models and Local LLMs

pi

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.

opencode

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

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.


Full CLI Reference

adr run

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.

ADR_MOUNTS — persistent mounts via config file

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; --mount flags from the CLI append afterward.

adr build

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.

adr fix-owner

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.

Platform Notes

Linux — file ownership

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/myapp

Alternatively, 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/workspace

Requires passwordless sudoadr fix-owner uses sudo chown under the hood.

macOS — no special action needed

Docker Desktop's VirtioFS layer remaps file ownership transparently.


Security Model

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

Why staging is needed

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).


Contributor Workflow

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:

  1. Define or refine the feature spec.
  2. Review the spec before writing code.
  3. Generate a task list from the reviewed spec.
  4. 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 codex

Why 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.


Adding More Agents

  1. Create agents/<name>/Dockerfile — install the agent, create a non-root user, set WORKDIR to /workspace, set ENTRYPOINT.
  2. Create agents/<name>/entrypoint.sh — translate AGENT_HEADLESS, AGENT_PROMPT, AGENT_SHELL, AGENT_PROVIDER, AGENT_MODEL env vars into the agent's own CLI flags.
  3. Add the agent name to KNOWN_AGENTS in the ADR CLI runtime under cli/runtime.sh.
  4. Document the config layout in README.md and add an example under config-examples/<name>/.

adr run is agent-agnostic by design once the entrypoint contract is in place.

About

Run coding agents in isolated docker containers

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors