Files
pi-devbox/DOCKER_HUB.md
T
joakimp b8d818ed99
Lint / hadolint (push) Successful in 9s
Lint / doc-drift (push) Successful in 13s
Lint / skill-floor (push) Successful in 15s
Lint / actionlint (push) Successful in 18s
fix(docs): correct the Hub size claims, and gate them so they cannot rot again
DOCKER_HUB.md claimed ~1.1 GB for :latest while Docker Hub served 1.37 GB --
20% wrong, drifting quietly across eight releases, and POSTed to Docker Hub by
update-description every time. It is the first number a stranger reads about
this image.

Root cause is structural, not carelessness: every OTHER claim check-doc-drift.sh
guards is anchored to a build file (a pin, an ARG, a placeholder), so it cannot
rot without someone editing the thing it describes. Nothing in this repo states
the image size, so nothing measured it.

Corrected against measured full_size after v1.9.2 published (amd64/arm64):
  :latest         ~1.1  -> ~1.23 GB   (1.228 / 1.211)
  :latest-studio  ~1.15 -> ~1.25 GB   (1.255 / 1.238)
  :base-latest    ~1.0  -> ~1.17 GB   (1.167 / 1.151)
full_size tracks the FIRST manifest entry (amd64), not the sum across arches --
measured: full_size=1.228, amd64=1.228, arm64=1.211, sum=2.439 -- which is what
the table's per-arch "Size (compressed)" column claims.

New check 8 gates those claims against Hub. Fails on drift; SKIPS LOUDLY via a
new skip() helper (counted, named in the summary) when curl/python3 are absent,
the API is unreachable, or SKIP_SIZE_CHECK=1. A skip is deliberately neither OK
nor a failure: printing an unverified claim as OK is the habit this file exists
to break, and failing on Docker Hub's uptime would hold releases hostage to a
third party. Not wired into hooks/pre-push, so pushes stay offline.

Two bugs caught by writing the expected exit code down before running the check:

  1. The tolerance would have missed its own motivating case. The percentage is
     computed against the MEASURED size, but the 20% first chosen came from the
     claim-relative figure; the real drift was |1.1-1.37|/1.37 = 19.7% and would
     have passed. Now 15%, inside a window with both bounds measured: above the
     largest legitimate skew (11.4%, a claim describing the published release
     while the next tag changes the size) and below the rot it must catch.

  2. A `|| true` on the python invocation made the gate FAIL OPEN -- it printed
     DRIFT and exited 0. Removed; the outer `|| SIZE_RC=$?` satisfies set -e
     without swallowing the code. Verified two-sided: 19% drift exits 1 at the
     default tolerance and 0 at SIZE_TOLERANCE_PCT=25, so the threshold does the
     work rather than the ordering.

NOT covered, and the script says so where a reader will see it: README.md's
~3.2 GB figures are UNCOMPRESSED and the registry exposes compressed sizes only,
so measuring them needs a real pull. A green check 8 says nothing about them.

Verified: check-doc-drift.sh rc=0 on the corrected tree, rc=1 on injected drift,
rc=0 with --warn-only, SKIP+rc=0 on the offline path (proxy to a dead port);
shellcheck clean at default severity (the sed single-quote SC2016 carries a
disable with its reason); lint-shell.sh 16 files clean.
2026-09-14 20:33:40 +02:00

11 KiB

pi-devbox

A self-contained Docker container for the pi coding-agent — pi + companion repos + MemPalace + a curated set of dev tooling, ready to run.

Current :latest ships pi {{PI_VERSION}} (resolved at build time; see Versioning).

Image variants

Tag Architectures Size (compressed) What you get
joakimp/pi-devbox:latest amd64, arm64 ~1.23 GB Self-contained: base + pi {{PI_VERSION}} + companions
joakimp/pi-devbox:vX.Y.Z amd64, arm64 same Pinned semver release
joakimp/pi-devbox:latest-studio amd64, arm64 ~1.25 GB latest + pi-studio: browser prompt editor, KaTeX/Mermaid preview, tmux-backed literate REPLs
joakimp/pi-devbox:vX.Y.Z-studio amd64, arm64 same Pinned semver studio release
joakimp/pi-devbox:base-latest amd64, arm64 ~1.17 GB Base layer alias (internal building block; pull :latest instead)
joakimp/pi-devbox:base-<hash> amd64, arm64 ~1.17 GB Content-addressed base; immutable. Stable parent for variant rebuilds.

pi-studio (-studio tags): launch with /studio --no-browser --port 8765 inside a pi session. The server binds 127.0.0.1 inside the container, so reach it via host networking or a loopback bridge (and ssh -L for a remote host; mosh needs a parallel ssh -L). Full recipe: README → Using pi-studio.

Quick start

One-shot, no persistence:

docker run -it --rm \
  -v "$PWD":/workspace \
  -v "$HOME/.ssh":/home/developer/.ssh:ro \
  -e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
  joakimp/pi-devbox:latest pi

For a fully-configured environment with persistent settings, MemPalace memory, neovim plugins, and shell history surviving container recreation, use docker-compose. You don't need to clone the repo — just grab two template files:

mkdir -p ~/pi-devbox && cd ~/pi-devbox
curl -O https://gitea.jordbo.se/joakimp/pi-devbox/raw/branch/main/docker-compose.yml
curl -fsSL https://gitea.jordbo.se/joakimp/pi-devbox/raw/branch/main/.env.example -o .env
# Edit .env — set WORKSPACE_PATH, an LLM API key (ANTHROPIC_API_KEY,
# OPENAI_API_KEY, GEMINI_API_KEY, or AWS_*), and your git identity.
docker compose run --rm devbox pi

Full setup guide — authentication for each provider (Anthropic, OpenAI, Gemini, AWS Bedrock SSO + static), persistence model, configuration reference, build args, troubleshooting: https://gitea.jordbo.se/joakimp/pi-devbox#readme

What's inside

pi and companions

  • pi {{PI_VERSION}} (@earendil-works/pi-coding-agent) — installed at /usr/bin/pi, pinned to an audited version (not npm latest)
  • pi-atelier — TUI sidebar (ordered panels, split-pane, themes), vendored at /opt/pi-atelier and pinned to an audited tag; the exact tag is in the image labels (se.jordbo.pi-devbox.pi-atelier-version) and /etc/pi-devbox/build-manifest.json
  • pi-toolkit — keybindings (mosh/tmux-friendly Shift+Enter, Ctrl+J, Alt+J newline bindings), AWS env loader, settings template
  • pi-extensions — 7 user-facing extensions: ext-toggle, mcp-loader, todo, ssh-controlmaster, notify, git-checkpoint, confirm-destructive
  • fork (pi-fork) and recall (pi-observational-memory) tools
  • mempalace bridge — MCP extension auto-symlinked so pi reads/writes the host-mounted palace
  • image-baked agent skills — skills under /usr/local/share/pi-devbox/skills/ (e.g. pi-devbox-environment, which teaches agents the container's persistence/networking/DNS/tmux/REPL specifics) are symlinked into ~/.agents/skills/ on start, available with or without a mounted skillset repo

The entrypoint deploys/registers all of these on first container start. Re-running is idempotent and preserves user edits.

MemPalace (persistent agent memory)

  • MemPalace + MCP server — semantic search over conversation history, knowledge graph, diary; queryable via 29 mempalace_* tools inside pi
  • ChromaDB ONNX embedding model pre-warmed at build time (all-MiniLM-L6-v2)
  • Bind-mount your host's ~/.mempalace and the host-pi and container-pi share one brain

Document and image tooling

  • pandoc — universal Markdown↔HTML/Org/RST/etc. conversion. Useful well beyond pi: agent-driven doc exports, format conversion, etc.
  • Typst — markup-based typesetting, used as pandoc's --pdf-engine
  • graphviz (dot) — diagram rendering pipelines
  • imagemagick (magick) — image conversion / resizing

Browser automation

  • agent-browser — CLI for driving a real browser (open pages, click/fill/eval, snapshot the DOM, screenshots) so agents can verify front-end work instead of guessing
  • Playwright + a headless Chromium are pre-installed and pinned together; AGENT_BROWSER_EXECUTABLE_PATH is preset to the baked browser, so agent-browser open <url> works out of the box with no setup
  • socat — TCP bridge used to expose the pi-studio server outside the container's loopback

Modern CLI tooling

  • Editor: neovim (system-wide termguicolors default; bring your own config/plugins), tmux (configured for 0-indexed sessions)
  • Search/nav: ripgrep, fd, fzf, zoxide
  • Display: bat, eza, htop, tree
  • Data: jq, yq
  • Help: tldr (tealdeer — Rust port; run tldr --update once to populate cache)
  • Git: git-lfs, git-crypt, gitleaks (for pre-commit secret scanning)
  • Build: gcc, g++, make, patch
  • Misc: gosu, age, rsync, less

Language toolchains

  • Python: system Python 3 + uv (preferred) for fast Python package management. Run any Python REPL/notebook stack on demand without bloating the image:
    uv run --with ipython ipython
    uv run --with jupyterlab jupyter lab --no-browser --port 8888
    uv run --with marimo marimo edit
    
  • Node.js v24 LTS + npm (used by pi itself)
  • Rust — rustup-init is on PATH; install toolchains on demand
  • Go — opt-in via --build-arg INSTALL_GO=true if rebuilding from source

Cloud + secrets

  • AWS CLI v2 — for SSO + Bedrock auth (pi's preferred LLM provider for the maintainer's setup)
  • Gitea MCP server — for Gitea API access from inside pi
  • age, git-crypt — encryption tooling

SSH and networking

  • OpenSSH client with ControlMaster auto preconfigured on a writable socket path (/tmp/sshcm/). Mitigates ssh banner-exchange failures behind CGNAT-restricted residential ISPs (~4-flow caps). A read-only ~/.ssh carrying a per-host ControlPath (common CGNAT configs) is handled too — redirected to a writable socket dir for both pi --ssh and dssh/dscp.
  • A LAN-access helper that auto-configures ssh jump-via-host on VM-backed hosts (OrbStack / Docker Desktop on macOS) so the container can reach the host's directly-attached LAN peers (dssh <peer> alias; DEVBOX_LAN_ACCESS / HOST_SSH_USER).

Versioning

From v1.0.0 onward, pi-devbox uses semver:

  • Major — architectural changes. v1.0.0 is the first decoupled release, where pi-devbox got its own self-contained build chain (previously it was a thin re-brand of opencode-devbox's pi-only variant).
  • Minor — new image variants, significant base additions.
  • Patch — pi version bumps, smaller fixes.

The pi binary version inside any given release is shown in this description (currently {{PI_VERSION}} for :latest) and asserted by smoke tests to match what's documented — version drift is caught at CI time, not on user pull.

Pre-v1.0.0 history. Tags v0.74.0…v0.79.0 followed the pi npm version directly (v{pi_version}[letter]). Those images remain on Hub but are deprecated in favor of :latest / :v1.X.Y. The legacy :base-pi-only* tags were CI artifacts of the old opencode-devbox-based build pipeline; they will be removed in a future opencode-devbox v2.0.0.

Build pipeline

pi-devbox is built in two phases:

  1. Base (Dockerfile.base) → base-<hash> tag, content-addressed over Dockerfile.base + rootfs/ + entrypoint*.sh. Rebuilt only when those change.
  2. Variant (Dockerfile.variant) → :latest and :vX.Y.Z. FROMs the base, adds the pi install + companions.

base-latest is an alias of the most recent base.

Persistent state

User edits and pi-installed packages survive container recreation when you mount these named volumes. Use the included docker-compose.yml and they're set up automatically.

Volume Mount point What it holds
devbox-pi-config /home/developer/.pi/ pi settings, extension toggles, sessions, user-installed pi packages (npm install -g, pi install npm:…)
devbox-shell-history /home/developer/.cache/bash bash history
devbox-zoxide /home/developer/.local/share/zoxide zoxide directory jump database
devbox-nvim-data /home/developer/.local/share/nvim neovim plugin & Mason package state
devbox-uv /home/developer/.local/share/uv uv Python installs and tool cache
devbox-ssh-local /home/developer/.ssh-local LAN-jump key (one-time host authorization survives recreate)

Optional volumes for MemPalace (commented out by default — uncomment in docker-compose.yml to persist conversation memory across restarts):

Volume Mount point What it holds
devbox-palace /home/developer/.mempalace palace data (drawers, knowledge graph, embeddings)
devbox-chroma-cache /home/developer/.cache/chroma ChromaDB embedding model cache (~80 MB, can be rebuilt)

User-installed pi packages

NPM_CONFIG_PREFIX is set inside the container to /home/developer/.pi/npm-global. Anything you pi install npm:<pkg> or npm install -g lands on the devbox-pi-config named volume — survives container recreation and image rebuilds. A user-installed pi wins over the baked one via PATH order, so you can pin a different pi version without rebuilding the image.

Source

License

MIT (the image; pi and the bundled tools each carry their own licenses). See LICENSE and THIRD_PARTY.md in the source repo.