Skip to main content

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/

PyPI version Python versions MIT Licence

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:

  1. Project-level TOML (<workdir>/.paddock/config.toml)

  2. User-level TOML (~/.config/paddock/config.toml)

  3. [projects."<path>"] overrides in the user TOML

  4. PADDOCK_* environment variables

  5. 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 ~/.config/paddock/ (user-level) or <project>/.paddock/ (project-level). Both 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.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for phx-paddock 1.0.0
File Size Uploaded
phx_paddock-1.0.0.tar.gz 29.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for phx-paddock 1.0.0
File Interpreter ABI Platform
phx_paddock-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 67.5 kB

Release files / phx_paddock-1.0.0.tar.gz

Download URL phx_paddock-1.0.0.tar.gz
Size 29.3 kB
Tags Source
SHA-256 checksum
How to use checksums
fc6c56a29fcf4defe336fb120eb4cb884cd0fd82fa2f925c10d22eaeb65b1893
BLAKE2b-256 checksum
How to use checksums
6f2b9933a061a2c046b11fea1332b4427a786779171e37ff8407809e9b491c2a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","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.0.0-py3-none-any.whl

Download URL phx_paddock-1.0.0-py3-none-any.whl
Size 38.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
62f03ce0c3e74c36d6c12bf4564feda4d449c566ce83cda12fe93266d3c35714
BLAKE2b-256 checksum
How to use checksums
ac18643bd97ddfe057a04186dca0051ec86cda20ea41ae3c7feeff2805f54c5b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.11 {"installer":{"name":"uv","version":"0.11.11","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release history Release notifications | RSS feed

1.1.0

2 release files

This release

1.0.0 This release

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page