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
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.
396 lines
13 KiB
Markdown
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
|