Files
pi-devbox/README.md
T
pi c1154f1fa6
Publish Docker Image / resolve-versions (push) Successful in 5s
Publish Docker Image / base-decide (push) Successful in 12s
Publish Docker Image / build-base (push) Successful in 45m47s
Publish Docker Image / smoke (push) Successful in 8m18s
Publish Docker Image / build-variant (push) Successful in 22m41s
Publish Docker Image / update-description (push) Failing after 9s
Publish Docker Image / promote-base-latest (push) Successful in 14s
v1.0.0: decouple from opencode-devbox
Self-contained build chain — own Dockerfile.base + Dockerfile.variant
+ entrypoint scripts + rootfs + CI pipeline. Previously v0.79.0 and
earlier were thin re-brands of opencode-devbox's pi-only variant
(joakimp/pi-devbox:base-pi-only built by opencode-devbox CI).

Architectural changes:
- Replace 5-line Dockerfile shim with full base+variant pair.
- Adapt CI workflow from opencode-devbox/docker-publish-split.yml,
  simplified to a single variant. Includes content-addressed base hash,
  PI_VERSION concrete-resolution to defeat registry-buildcache footgun,
  crane-based base-latest promotion, and the c6f9d11 smoke-test gate.
- pi-devbox releases no longer require rebuilding opencode-devbox first.

Base image additions:
- pandoc, graphviz, imagemagick, yq — broadly useful, ~260 MB total.
- tldr (tealdeer) — Rust port replaces Node tldr global, saves 135 MB.
- /etc/tmux.conf with base-index 0 + pane-base-index 0 — required for
  the planned :latest-studio variant; pi-studio hard-codes :0.0 target.

Smoke test:
- New checks for pandoc, graphviz, imagemagick, yq, tldr, tmux config,
  /tmp/sshcm directory.
- Image-size measurement now sums docker history layers (the prior
  inspect --format='{{.Size}}' returned only the variant-unique layer
  with the new base/variant split, understating by 2+ GB).
- Threshold 2850 → 3500 MB to absorb base additions + arch margin.

Image size:
- Local arm64 build: 3.20 GB. ~390 MB up from prior pi-only equivalent.
- Will tighten threshold once amd64 actuals settle in CI.

Pre-1.0 history preserved at tag pre-v1.0.0-decouple-backup.

Future work:
- v1.1.0: :latest-studio variant (adds pi-studio).
- v1.2.0: :latest-studio-tex variant (adds texlive-xetex for PDF).
- opencode-devbox v2.0.0 will retire INSTALL_PI / pi-only paths.
2026-06-10 01:14:07 +02:00

396 lines
13 KiB
Markdown

# pi-devbox
A self-contained Docker image for running [pi](https://pi.dev) — the pi
coding-agent — in an isolated, reproducible Linux environment with a
curated set of developer tooling, AI memory, and shell improvements.
pi-devbox is opinionated about what's inside but unopinionated about how
you use it: a single `docker compose up` gives you an interactive
container with pi, a stack of modern CLI tools, MemPalace for persistent
agent memory across sessions, and a UID-aligned `/workspace` mount so
files you edit inside the container appear with your normal ownership
on the host.
## What's inside
### The pi coding-agent
- `pi` — the pi-coding-agent CLI (`@earendil-works/pi-coding-agent`)
- `pi-toolkit` — keybindings, AWS env loader, settings template
- `pi-extensions` — TypeScript extensions for pi (preview, MCP bridges,
mempalace integration, etc.)
- `pi-fork` — the `fork` tool for spawning sub-agents
- `pi-observational-memory` — the `recall` tool for session compaction
### MemPalace (AI memory)
- `mempalace` — local-first agent memory system (29 MCP tools)
- `mempalace-toolkit` — bash wrappers for session/docs mining
- ChromaDB embedding model pre-warmed at build time (`all-MiniLM-L6-v2`)
The host-mounted palace at `~/.mempalace` is shared across the host and
this container so all your agents share one brain.
### Modern CLI tooling
| Tool | Purpose |
|---|---|
| `nvim` | Neovim text editor |
| `tmux` | Terminal multiplexer (configured for 0-indexed sessions) |
| `ripgrep`, `fd` | Fast file content / filename search |
| `fzf` | Fuzzy finder |
| `bat` | Syntax-highlighted `cat` |
| `eza` | Modern `ls` |
| `zoxide` | Smart `cd` |
| `jq`, `yq` | JSON / YAML query and transformation |
| `tldr` (tealdeer) | Quick command examples |
| `git`, `git-lfs`, `git-crypt` | Git + extensions |
| `gitleaks` | Secret scanning pre-commit hook |
| `gosu` | Privilege de-escalation in entrypoint |
| `htop`, `tree`, `less` | Inspection utilities |
### Document and image tooling
- `pandoc` — universal Markdown↔HTML/Org/RST/etc. converter
- `graphviz``dot` rendering for diagram pipelines
- `imagemagick` — image conversion / resizing (invoked as `magick`)
### Language toolchains
- `python3` + `python3-venv` + `python3-pip` (system Python)
- `uv` + `uvx` — fast Python package manager (preferred over pip/venv)
- `nodejs` (v22) + `npm`
- `gcc`, `g++`, `make` — C/C++ build tools
- `rustup-init` — Rust toolchain installer (toolchains opt-in at runtime)
- Optional `INSTALL_GO=true` build arg for Go
For Python REPLs and notebooks beyond the system interpreter, see the
[uv-driven REPL recipes](#uv-driven-repl-recipes) section.
### Cloud + secrets
- AWS CLI v2 — for SSO + Bedrock auth
- `gitea-mcp` — MCP server for Gitea API
- `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) by
multiplexing many ssh calls over one TCP flow.
- 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.
## Quickstart
### Prerequisites
- Docker or OrbStack (recommended on macOS)
- Optional: AWS credentials configured on the host if you'll use the
Bedrock LLM provider
### Pull and run
```bash
git clone https://gitea.jordbo.se/joakimp/pi-devbox
cd pi-devbox
cp .env.example .env # edit if needed
docker compose up -d
docker compose exec devbox bash
```
You're now in the container as user `developer` with `pi` on PATH and
your host workspace mounted at `/workspace`.
To start pi:
```bash
pi
```
First-run pi-toolkit and pi-extensions install steps run automatically
on container start; symlinks are written to `~/.pi/agent/` on the
named volume (so they persist across container recreations).
### Stop / recreate / update
```bash
docker compose down # stop, keep volumes
docker compose down -v # stop, wipe per-container volumes (palace data is bind-mounted, so unaffected)
docker compose pull # fetch latest image
docker compose up -d --force-recreate
```
## Image variants
Currently published:
| Tag | Includes | Size (approx.) |
|---|---|---|
| `joakimp/pi-devbox:latest` | base + pi + tooling | ~3.2 GB |
| `joakimp/pi-devbox:vX.Y.Z` | pinned-version equivalent | ~3.2 GB |
Planned for upcoming minor releases:
- `joakimp/pi-devbox:latest-studio` — adds [pi-studio](https://github.com/omaclaren/pi-studio)
for browser-based prompt editing, KaTeX/Mermaid preview, and
literate REPLs (Shell / Python / IPython / Julia / R / GHCi /
Clojure). Adds ~50 MB.
- `joakimp/pi-devbox:latest-studio-tex` — also adds `texlive-xetex`
for PDF export from Studio. Adds ~600 MB on top of `-studio`.
## docker-compose.yml — basic shape
```yaml
name: pi-devbox
services:
devbox:
image: joakimp/pi-devbox:latest
container_name: pi-devbox
stdin_open: true
tty: true
env_file: .env
environment:
- TZ=${TZ:-Europe/Stockholm}
- TERM=xterm-256color
- AWS_PROFILE=${AWS_PROFILE:-}
- AWS_REGION=${AWS_REGION:-eu-west-1}
volumes:
# Workspace: your host source tree, read-write
- ${HOST_WORKSPACE:-./workspace}:/workspace:rw
# SSH keys: read-only from host
- ${HOME}/.ssh:/home/developer/.ssh:ro
# AWS config: read-only from host
- ${HOME}/.aws:/home/developer/.aws:ro
# MemPalace: bind-mounted so host pi and container pi share a brain
- ${HOME}/.mempalace:/home/developer/.mempalace:rw
# Per-container persistent state
- devbox-pi-config:/home/developer/.pi
- devbox-bash-history:/home/developer/.cache/bash
- devbox-nvim-data:/home/developer/.local/share/nvim
- devbox-uv-tools:/opt/uv-tools
- devbox-chroma-cache:/home/developer/.cache/chroma
volumes:
devbox-pi-config:
devbox-bash-history:
devbox-nvim-data:
devbox-uv-tools:
devbox-chroma-cache:
```
See `.env.example` in the repo for available environment variables.
## uv-driven REPL recipes
uv is installed in the base image and is the recommended way to run
Python interpreters and notebooks without bloating the image:
| Goal | One-liner |
|---|---|
| IPython REPL | `uv run --with ipython ipython` |
| IPython + scientific stack | `uv run --with ipython --with numpy --with matplotlib --with pandas ipython` |
| JupyterLab (browser, port-forward needed) | `uv run --with jupyterlab jupyter lab --no-browser --port 8888` |
| Marimo (modern alternative) | `uv run --with marimo marimo edit --port 8889` |
For long-lived environments, prefer a project venv:
```bash
cd /workspace/myproj
uv init && uv add ipython numpy matplotlib
# then:
uv run ipython
```
`pyproject.toml` + `uv.lock` then capture the dependency state and
travel with the project in git.
uv only manages Python. For other languages:
| Toolchain | How to add |
|---|---|
| R | `sudo apt-get install r-base-core` (~200 MB) |
| GHCi (Haskell) | `sudo apt-get install ghc` (~700 MB) |
| Clojure | `sudo apt-get install clojure` (~150 MB + JVM) |
| Julia | `juliaup` is planned for an upcoming release |
These are runtime opt-ins and persist only in the container's writable
layer — they don't survive `docker compose down -v` or image updates.
## tldr — first-run cache
The `tldr` command (provided by tealdeer) shows a "Page cache not
found" message on first invocation. To populate the cache:
```bash
tldr --update
```
This fetches ~1500 command pages from the [tldr-pages](https://tldr.sh)
project and caches them in `~/.cache/tealdeer/`. After that, `tldr ls`,
`tldr docker`, etc. work instantly. Re-run `tldr --update` periodically
to refresh.
## Volumes and persistence
| Path inside container | Volume | What survives |
|---|---|---|
| `/workspace` | host bind-mount | host filesystem |
| `~/.ssh` | host bind-mount (read-only) | host filesystem |
| `~/.aws` | host bind-mount (read-only) | host filesystem |
| `~/.mempalace` | host bind-mount | host filesystem |
| `~/.pi` | named volume `devbox-pi-config` | `down -v` wipes |
| `~/.cache/bash` | named volume | `down -v` wipes |
| `~/.local/share/nvim` | named volume | `down -v` wipes |
| `/opt/uv-tools` | named volume | `down -v` wipes |
| `~/.cache/chroma` | named volume | `down -v` wipes |
Anything not on a volume is on the writable layer and is lost on
container recreate.
## MemPalace integration
MemPalace is installed in the base image and pre-warmed with the
ChromaDB ONNX embedding model so first-time semantic search is
instant.
The palace data lives at `~/.mempalace/palace` on the host
(bind-mounted into the container). This means:
- A pi running on the host and a pi running inside this container see
the same palace.
- SQLite's WAL mode handles concurrent reads + single writer cleanly,
so simultaneous use is safe in practice.
`mempalace-session` and `mempalace-docs` are on PATH for one-off
session/docs mining; the 29 MCP tools (search, kg-query, drawer-add,
diary-write, etc.) are wired into pi automatically by the pi-extensions
mempalace bridge.
## SSH and ControlMaster
The base image preconfigures `Host *` ssh defaults:
```
ControlMaster auto
ControlPath /tmp/sshcm/%r@%h:%p
ControlPersist 10m
```
The socket directory `/tmp/sshcm/` is created mode 700 on every
container start (per-container, tmpfs-friendly). Multiple ssh calls
to the same host within 10 minutes reuse the master TCP flow —
important on residential ISPs with CGNAT per-destination flow caps
(~4 flows on most European broadband; symptoms are
`kex_exchange_identification: Connection closed by remote host` on
the 5th+ concurrent ssh).
User-level overrides in `~/.ssh/config` win because Debian's
`/etc/ssh/ssh_config` includes `/etc/ssh/ssh_config.d/*.conf` before
the `Host *` block.
## tmux and 0-indexed sessions
The image installs `/etc/tmux.conf` with:
```
set -g base-index 0
set -g pane-base-index 0
```
This is the default tmux indexing. It's baked here because `pi-studio`
(planned for `:latest-studio`) hard-codes its tmux send target to
`<session>:0.0`. If you override `base-index` to 1 in a personal
`~/.tmux.conf`, pi-studio will fail with "can't find window: 0".
## AWS Bedrock auth
If you use Bedrock as pi's LLM provider:
1. Configure SSO on the host: `aws configure sso`
2. Bind-mount `~/.aws:/home/developer/.aws:ro`
3. Set `AWS_PROFILE` and `AWS_REGION` in `.env`
4. Inside the container: `aws sso login` if needed; pi picks up the
profile via the env vars.
The pi-toolkit AWS env loader (in `~/.pi/agent/`) prepares Bedrock
inference-profile model IDs (with `eu.` / `us.` prefixes) automatically.
## Build pipeline
pi-devbox is built from this repo's CI in two phases:
1. **Base** (`Dockerfile.base`) — produces `joakimp/pi-devbox:base-<hash>`
where `<hash>` is content-addressed over `Dockerfile.base`,
`rootfs/`, and `entrypoint*.sh`. Rebuilt only when these change.
2. **Variant** (`Dockerfile.variant`) — `FROM ${BASE_IMAGE}` and adds
the pi install. The `:latest` and `vX.Y.Z` tags are produced from
this layer; future Studio variants will extend further.
Tag naming:
| Tag | Stage |
|---|---|
| `base-<hash>` | base image — internal building block |
| `base-latest` | promoted alias of the most recent base |
| `latest`, `vX.Y.Z` | variant: base + pi |
CI resolves `PI_VERSION` to a concrete version string before building
to defeat a registry-buildcache hit on `npm install -g
pi-coding-agent@latest` (the build-arg string would otherwise be
byte-identical across releases and the layer would silently reuse the
previous version's bytes).
## Troubleshooting
### Image grew unexpectedly
`docker history joakimp/pi-devbox:latest` shows per-layer sizes. The
biggest layers are typically the apt block (~600 MB), pi npm install
(~330 MB), MemPalace + ChromaDB (~315 MB), AWS CLI (~270 MB), Node.js
(~200 MB).
### pi can't reach LAN peers on macOS
The LAN-access helper (`/usr/local/lib/pi-devbox/setup-lan-access.sh`)
auto-runs on container start and writes `~/.ssh-local/config` with a
ssh-jump-via-host configuration. Set `DEVBOX_LAN_ACCESS=jump` and
`HOST_SSH_USER=<your-mac-user>` in `.env` if auto-detection fails.
### Smoke-testing a local build
```bash
./scripts/smoke-test.sh joakimp/pi-devbox:latest
```
## Versioning and release
pi-devbox follows semver-ish:
- **Major** — architectural changes. `v1.0.0` is the first decoupled
release (independent of opencode-devbox).
- **Minor** — new variants, significant base additions.
- **Patch** — pi version bumps, smaller fixes.
The `pi --version` inside the image is asserted by smoke tests to
match the release tag's pi component, so version drift between the
image and the tag is caught at CI time.
## Acknowledgements
pi-devbox was originally a thin re-brand of the `pi-only` variant of
[opencode-devbox](https://gitea.jordbo.se/joakimp/opencode-devbox).
It was decoupled at `v1.0.0` so it could evolve at its own pace, with
self-contained docs and a focused, pi-centric image. Significant base
infrastructure (the SSH ControlMaster setup, MemPalace integration,
the entrypoint UID/GID dance) was adopted from there.
The pi coding-agent itself is [@earendil-works/pi-coding-agent](https://www.npmjs.com/package/@earendil-works/pi-coding-agent).
## License
MIT