paddock
Launch coding agents (or a plain shell) in isolated Docker containers, with the current working directory mounted as the workspace.
Usage guides and API reference: https://phx-paddock.readthedocs.io/
Overview
paddock assembles and executes a docker run command from a layered configuration system. Sources are merged in ascending priority — later sources overwrite earlier ones:
Project-level TOML (<workdir>/.paddock/config.toml)
User-level TOML ($XDG_CONFIG_HOME/paddock/config.toml, by default ~/.config/paddock/config.toml)
[projects."<path>"] overrides in the user TOML
PADDOCK_* environment variables
CLI flags
volumes entries are additive per host path — the same host path set by a higher-priority source replaces the earlier mapping.
The project-level file is off by default (blocked) — enable it in the user file’s [config.allowlist]; see project-level configuration and the allowlist.
Requirements
Python 3.12+
Docker (CLI must be available on PATH)
Installation
pip install phx-paddock
Or with uv:
uv tool install phx-paddock
Quick Start
Drop into a plain bash shell inside the current directory:
paddock --image=ubuntu:24.04 --agent=false
Run Claude Code in an isolated container:
paddock --image=my-claude-image --agent=claude
Print the assembled docker run command without executing it:
paddock --image=ubuntu:24.04 --agent=false --dry-run
Configuration
TOML files
Place a config.toml at $XDG_CONFIG_HOME/paddock/ (user-level) or <project>/.paddock/ (project-level). paddock uses ~/.config in place of XDG_CONFIG_HOME when it is unset, empty or not an absolute path. If XDG_CONFIG_HOME points somewhere other than ~/.config but holds no paddock/config.toml, paddock falls back to ~/.config/paddock/config.toml if one exists, with a warning; that fallback is deprecated and will be removed in paddock 2.0. If both exist, paddock reads the XDG one and warns that the other is ignored, so move an existing file rather than starting a new one. Both files are optional, and the project-level file is off by default until you opt in from your user config:
[config.allowlist]
project_toml = true
true is the blanket grant; a list such as project_toml = ["volumes"] permits only the keys it names. See project-level configuration and the allowlist for what each grant hands a committed file.
A config file looks like this:
agent = "claude"
image = "my-claude-image:latest"
network = "my-docker-network"
[volumes]
"/host/path" = "/container/path:ro"
[build]
dockerfile = "images/Dockerfile"
context = "."
policy = "daily"
[build.args]
AGENT = "claude"
PYTHON_VERSION = "3.13"
Config fields
Field |
Type |
Description |
|---|---|---|
agent |
string or false |
Agent key ("claude") or false for shell |
image |
string |
Docker image to run (required) |
network |
string (optional) |
Docker network to attach the container to |
volumes |
{host: container} map |
Extra bind-mounts; container path may end in :ro or :rw (bare path defaults to :ro) |
build |
sub-table (optional) |
Image auto-build settings (see below) |
Build sub-table
Field |
Type |
Description |
|---|---|---|
dockerfile |
string |
Path to the Dockerfile (required if build table is present) |
context |
string (optional) |
Docker build context path |
policy |
"always" / "daily" / "if-missing" / "weekly" |
When to rebuild the image |
args |
{name: value} map (optional) |
Build-time --build-arg values |
Environment variables
Six config fields can be set via an environment variable, by uppercasing the field name and prefixing it with PADDOCK_. Nested keys are joined with _:
PADDOCK_AGENT=claude
PADDOCK_BUILD_CONTEXT=.
PADDOCK_BUILD_DOCKERFILE=images/Dockerfile
PADDOCK_BUILD_POLICY=daily
PADDOCK_IMAGE=my-claude-image
PADDOCK_NETWORK=my-docker-network
volumes and build.args have no environment-variable form — set them in a TOML file, or pass --volume / --build-args-KEY=VALUE on the command line. PADDOCK_BUILD_ARGS is ignored rather than rejected. Any other unrecognised PADDOCK_* name is a fatal config error, so a typo stops the run:
[env:foo] Unexpected key "foo".
CLI flags
paddock [FLAGS] [COMMAND...]
paddock [FLAGS] -- [ARGS...]
--agent AGENT Agent key (e.g. "claude") or "false" for a shell
--build-args-KEY=VALUE Build-time ARG (repeatable)
--build-context PATH Docker build context
--build-dockerfile PATH Path to Dockerfile
--build-policy POLICY Build policy (always|daily|if-missing|weekly)
--dry-run Print the docker command and exit without running it
--image IMAGE Docker image
--network NETWORK Docker network
--quiet Suppress all logging and the docker command printout
--version Print the paddock version and exit
--volume HOST:CONTAINER[:MODE] Extra bind-mount (repeatable)
--workdir PATH Host path to use as the workspace (default: CWD)
--workdir is resolved to an absolute real path — symlinks followed — before it is used for the [projects] lookup and for the mounts.
paddock exits with the container’s exit status; --dry-run exits 0, a config error exits 1 and an unknown flag exits 2.
With no command before it, everything after -- is appended to the agent’s command:
# runs: claude "fix this bug"
paddock --agent=claude -- "fix this bug"
# runs: claude --resume
paddock --agent=claude -- --resume
The first positional argument before -- starts a command that replaces the agent’s, running in a container still configured for that agent (its volumes and build args). Everything after it — -- and anything that looks like a paddock flag included — belongs to that command, so put paddock flags first:
# runs: /bin/bash
paddock --agent=claude /bin/bash
A prompt without -- in front is therefore a command: paddock --agent=claude "fix this bug" tries to execute fix this bug. Anything else before -- must be a paddock flag, spelled in full, so paddock --agent=claude --resume fails.
Agents
claude
Runs claude inside the container. Mounts ~/.claude from the host to /root/.claude:rw, and ~/.claude.json to /root/.claude.json:rw if it exists, so authentication, onboarding and configuration persist between sessions.
false (shell)
Runs /bin/bash. Useful for exploring the container environment or running ad-hoc commands without a coding agent.
Adding agents
Additional agents can be registered via the paddock.agents entry-point group in any installed package:
[project.entry-points."paddock.agents"]
my-agent = "mypackage.agents:MyAgent"
Each agent must subclass paddock.agents.BaseAgent and implement get_command() and get_volumes().
Docker Image
A ready-to-use Dockerfile is included in images/. It installs Python (via the deadsnakes PPA), Node.js, and the selected coding agent.
Build arguments:
ARG |
Default |
Description |
|---|---|---|
UBUNTU_VERSION |
24.04 |
Ubuntu base image tag |
AGENT |
none |
claude or none |
NODE_VERSION |
22 |
Node.js major version |
PYTHON_VERSION |
3.13 |
Python version (installed from deadsnakes) |
Build the image manually:
docker build \
--build-arg AGENT=claude \
-t my-claude-image \
-f images/Dockerfile .
Or set a [build] table in your config and let paddock build it automatically according to your chosen policy.
For what a custom image needs, and troubleshooting one that won’t start, see custom Docker images.
Licence
MIT — see LICENCE.txt.
Metadata
Release files for phx-paddock 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| phx_paddock-1.1.0.tar.gz | 30.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| phx_paddock-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 69.1 kB
Release files / phx_paddock-1.1.0.tar.gz
| Download URL | phx_paddock-1.1.0.tar.gz |
|---|---|
| Size | 30.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
47fded6bc50fdc8386bf13d2a86405faa3515f016c8daecd4951dc0227406582
|
|
BLAKE2b-256 checksum How to use checksums |
d9816b84fa6ab95f5436b5da937c16972e223de30d0490442ceb5ba78a28286a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / phx_paddock-1.1.0-py3-none-any.whl
| Download URL | phx_paddock-1.1.0-py3-none-any.whl |
|---|---|
| Size | 39.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fd5544870d33b2574e8d2b6bd56a55368d4f95c716b6a8a19c483c9e2f6485e8
|
|
BLAKE2b-256 checksum How to use checksums |
29aad9949f1e52077dfd9b3f509a5c8cf92f739b5c1ef4626702d38faba190c1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.12.23 {"installer":{"name":"uv","version":"0.12.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|