Deploying Paddock
You can run Paddock on your laptop — npx @edspencer/paddock is one command
and the fastest way to try it (see
Getting started) — but that’s not how it’s meant
to live. Paddock’s whole point is persistent, resumable
agents you can reach from anywhere — and a laptop that sleeps, closes, and moves
around defeats that.
Pick an always-on host
Section titled “Pick an always-on host”It does not have to be fancy. Anything that stays on and sips power works:
- A mini PC (Intel N100-class), an Intel NUC, or an old thin client.
- A Raspberry Pi 4/5 (Paddock’s image is multi-arch —
arm64andamd64). - A NAS that runs Docker (Synology, etc.).
- An LXC container or VM on a home server (Proxmox, etc.) — this is what the author uses; see A home-lab setup.
- A small cloud VPS, if you’d rather not host at home.
The common thread: on 24/7, low idle power, and something you (and only you) can reach on the network.
Run it
Section titled “Run it”Paddock ships as a Docker image, which is the simplest way to deploy on any of the hosts above:
docker run -d --name paddock -p 127.0.0.1:7233:7233 \ -e CLAUDE_CODE_OAUTH_TOKEN=… `# or ANTHROPIC_API_KEY` \ -e PADDOCK_DATA_DIR=/data \ -e PADDOCK_DANGEROUSLY_ALLOW_OPEN=1 `# every container run needs this` \ -v paddock-data:/data \ --restart unless-stopped \ ghcr.io/edspencer/paddock:latestPADDOCK_DANGEROUSLY_ALLOW_OPEN=1 is not optional here: in a container the app binds
0.0.0.0, and the fail-closed
bind guard refuses to
boot on a non-loopback bind under the default PADDOCK_AUTH_MODE=none. It is safe in
this command only because the publish is 127.0.0.1-bound — the container namespace
plus that publish is the real boundary. Publish on a routable address instead and you
should drop this flag and set a real auth mode; see
Securing Paddock.
--restart unless-stopped matters here — it’s what makes Paddock come back after a
reboot or power blip, which is the whole point of an always-on host. A
docker-compose file (see Getting started) is the tidy way to
keep the config in version control.
Two image tags are published from the same source: :latest (base — the app plus
git/openssh-client/gh/claude) and :devbox (base plus the coding-agent
toolbox — pm, ffmpeg, a headless browser, the Docker CLI, kubectl, and a
scripting kit — for projects that build and run apps).
The Dev Box flavor is the full, current breakdown of what
each image carries.
Data & backups
Section titled “Data & backups”Everything Paddock persists lives under PADDOCK_DATA_DIR — projects, chat
transcripts, and its sidecar state. Put it on a named volume or a real disk you
back up. The data directory is git-friendly (projects are directories); a periodic
snapshot or offsite copy is enough to recover.
One process per port
Section titled “One process per port”Paddock is one process per data root + port. To run several (say, one for work
and one for home), start one container each with its own PADDOCK_DATA_DIR and
PORT, and front them with a reverse proxy that maps a hostname to each. Nothing is
shared between instances except the host.
Keeping it current
Section titled “Keeping it current”New releases are published to ghcr.io/edspencer/paddock. Re-pull and recreate the
container to update, or automate it — a tool like Watchtower, or a small “watch for a
new release and redeploy” job, keeps you on the latest without manual work. (The
author’s setup auto-deploys new tagged releases; see
A home-lab setup.)
paddock is on PATH in the image, so you can ask the container what it would do:
docker exec paddock paddock update --checkPaddock 0.82.0 is available (you have 0.81.0). Run: docker compose pull && docker compose up -d (image ghcr.io/edspencer/paddock:0.82.0)Nothing installs anything inside a container — the image sets PADDOCK_IN_DOCKER=1, so
paddock update there prints the pull command and exits 5. See
Updating Paddock for the rest.
Don’t restart it mid-turn
Section titled “Don’t restart it mid-turn”An automatic updater will, sooner or later, recreate the container while a chat turn is in
flight. A turn is a live claude subprocess writing a transcript; it cannot be resumed
from where it was interrupted, so that turn is simply lost — and the person who asked for
it finds a half-finished answer with no explanation.
paddock busy is there for exactly this. It exits 0 when nothing would be lost, 75
when something would, and 3 when nothing is running — and 75 is EX_TEMPFAIL, which is
the code Watchtower’s pre-update hook reads as “skip this one”. Turn lifecycle hooks on
and label the container:
services: paddock: image: ghcr.io/edspencer/paddock:latest labels: com.centurylinklabs.watchtower.lifecycle.pre-update: "paddock busy" watchtower: image: containrrr/watchtower environment: WATCHTOWER_LIFECYCLE_HOOKS: "true" volumes: - /var/run/docker.sock:/var/run/docker.sockWatchtower runs the hook inside the container before it stops it; a non-zero exit skips
the update and it tries again on its next pass. Without
WATCHTOWER_LIFECYCLE_HOOKS=true the label is read and the hook is never run, which looks
exactly like a hook that always says yes.
An Ansible task or a deploy job does the same thing with a retry:
- name: wait for Paddock to be idle command: docker exec paddock paddock busy register: idle until: idle.rc == 0 retries: 30 delay: 60 changed_when: falseIf something other than Watchtower owns the upgrade, set PADDOCK_UPDATE_HINT to the
command that performs it; paddock update then prints that sentence instead of guessing at
a docker compose line.
Making it reachable — safely
Section titled “Making it reachable — safely”Bind Paddock to the host and reach it through a reverse proxy that terminates TLS
(Caddy, nginx, Traefik…), so you get https://paddock.example.com instead of a raw
port. Two ways to reach it from outside your home:
- Keep it private (recommended for most people). Don’t expose it to the internet at all — reach it over a VPN or an overlay network (WireGuard, Tailscale, etc.). Simple and very safe.
- Expose it through the proxy — only if the proxy authenticates every request (see below).
paddock-deploy— copy-and-go recipes for Docker, Proxmox, Kubernetes, and a Basic-Auth sidecar.- Securing Paddock — authentication in front of Paddock.
- A home-lab setup — a full always-on, composed deployment.
- Environment variables — every
PADDOCK_*setting.