# pi-devbox A Docker container with [pi coding-agent](https://github.com/earendil-works/pi) pre-installed, built on the [opencode-devbox](https://gitea.jordbo.se/joakimp/opencode-devbox) base image. Persistent state, full dev toolchain, MemPalace memory, and provider-agnostic LLM auth — in one `docker compose run`. > **Hub:** [`joakimp/pi-devbox`](https://hub.docker.com/r/joakimp/pi-devbox) · multi-arch (amd64 + arm64) > **Source:** [gitea.jordbo.se/joakimp/pi-devbox](https://gitea.jordbo.se/joakimp/pi-devbox) --- ## What's inside pi-devbox is a re-brand of the **opencode-devbox `pi-only` variant** — it `FROM`s `opencode-devbox:latest-pi-only` and adds no layers of its own. Everything below is inherited from that single source of truth. The pi-only variant is built with `INSTALL_OPENCODE=false`, so this image is lean and pi-focused — no opencode. Base tooling: - **Debian trixie** (current stable) - **Node.js** (LTS), **uv** (Python), **rustup** (Rust on-demand) - **AWS CLI v2** (with Bedrock support) - **MemPalace** + MCP server — persistent agent memory across sessions; queryable via `mempalace_*` tools inside pi - **Gitea MCP** server - **Dev tools**: neovim (LazyVim), tmux, bat, eza, fzf, zoxide, ripgrep, jq, git-lfs, make - **Shell**: bash with history tuning, prefix-search, fzf/zoxide integration - **Host-OS-agnostic LAN access** — on VM-backed hosts (macOS OrbStack / Docker Desktop) the entrypoint sets up the host as an SSH jump so you can reach LAN peers (`dssh` alias; `DEVBOX_LAN_ACCESS`/`HOST_SSH_USER` env). No-op on native Linux. pi and companions: - **pi** ([`@earendil-works/pi-coding-agent`](https://www.npmjs.com/package/@earendil-works/pi-coding-agent)) — baked at `/usr/bin/pi`, version pinned by the pi-only base build - **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — mosh/tmux-friendly keybindings (Shift+Enter, Ctrl+J, Alt+J newline), AWS env loader, settings template - **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — 7 extensions: `ext-toggle`, `mcp-loader`, `todo`, `ssh-controlmaster`, `notify`, `git-checkpoint`, `confirm-destructive` - **`fork` tool** ([pi-fork](https://github.com/elpapi42/pi-fork)) and **`recall` tool** ([pi-observational-memory](https://github.com/elpapi42/pi-observational-memory)) — baked into `/opt` and registered at runtime - **mempalace bridge** — auto-symlinked MCP extension so pi reads/writes the same palace as opencode-devbox's palace (opencode itself is **not** included — that's the difference from `opencode-devbox:latest-with-pi`. If you want both opencode and pi in one image, use that variant instead.) The entrypoint deploys/registers all of these on first container start. Idempotent and preserves user edits. --- ## Quick start (no git clone) If you just want to run pi-devbox and don't plan to modify the source, grab the two template files and go: ```bash mkdir -p ~/pi-devbox && cd ~/pi-devbox # Pull the docker-compose.yml and .env template 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 — at minimum set WORKSPACE_PATH, an LLM API key, and your git identity $EDITOR .env # Pull and run pi docker compose run --rm devbox pi ``` `docker compose run --rm devbox` (no command) drops you into bash; you can then run `pi`, `aws sso login`, etc. manually. To attach a second terminal to the same container (e.g. shell while pi is running): ```bash docker compose exec -u developer devbox bash ``` --- ## Quick start (with git clone) If you want to follow upstream changes, run a customized fork, or rebuild the image yourself: ```bash git clone https://gitea.jordbo.se/joakimp/pi-devbox cd pi-devbox cp .env.example .env $EDITOR .env docker compose run --rm devbox pi ``` --- ## Authentication pi reads provider credentials from environment variables, which the container picks up from `.env` automatically. ### Anthropic (Claude) ```ini ANTHROPIC_API_KEY=sk-ant-... ``` Generate a key at . ### OpenAI ```ini OPENAI_API_KEY=sk-... ``` ### Google Gemini ```ini GEMINI_API_KEY=... ``` ### AWS Bedrock (e.g. Claude on Bedrock) Two paths — pick one: **A) Static credentials** (simplest, lower-trust environments only): ```ini AWS_REGION=eu-west-1 AWS_ACCESS_KEY_ID=AKIA... AWS_SECRET_ACCESS_KEY=... ``` **B) AWS SSO** (recommended for corporate AWS, requires mounting `~/.aws`): ```ini AWS_REGION=eu-west-1 AWS_PROFILE=your-profile ``` Then in your `docker-compose.yml`, uncomment the `~/.aws` bind-mount: ```yaml volumes: - ~/.aws:/home/developer/.aws ``` Inside the container, run `aws sso login` once per session. The token cache lives on the bind-mount, so subsequent `pi` invocations pick it up automatically. The pi-toolkit's `pi-env.zsh` (deployed to `~/.config/pi/`) auto-sources `AWS_PROFILE`/`AWS_REGION` whenever a shell starts. ### First-run pi configuration On first start, pi reads `~/.pi/agent/settings.json` (auto-bootstrapped from the pi-toolkit template). Edit it inside the container to pick a default provider/model: ```bash docker compose exec -u developer devbox bash $EDITOR ~/.pi/agent/settings.json ``` The file is rewritten by pi at runtime (e.g. `lastChangelogVersion`), so it lives on the `devbox-pi-config` named volume — your edits persist across container recreation. For pi's full configuration model (provider list, model overrides, MCP integration, themes, extensions): . --- ## Persistent state Persistent state is what makes the difference between "use this once" and "make it my long-term coding environment". Everything important survives `docker compose down` and image upgrades; only `docker compose down -v` wipes the volumes. | Volume | Mount point | What survives | Notes | |---|---|---|---| | `devbox-pi-config` | `/home/developer/.pi/` | pi settings.json, extension toggles, sessions, user-installed pi packages | `NPM_CONFIG_PREFIX` set inside the container so `pi install npm:…` and `npm install -g` lands here automatically | | `devbox-shell-history` | `/home/developer/.cache/bash` | bash history | Across container recreate | | `devbox-zoxide` | `/home/developer/.local/share/zoxide` | zoxide directory jump history | The `z`/`zi` shortcuts remember where you've been | | `devbox-nvim-data` | `/home/developer/.local/share/nvim` | neovim plugin & Mason package state | LazyVim plugins persist | | `devbox-uv` | `/home/developer/.local/share/uv` | uv-managed Python installs and tool cache | `uv tool install` results live here | ### Optional persistent volumes These are commented out in `docker-compose.yml` by default. Uncomment them if you want the corresponding state to persist: | Volume | Mount point | What survives | |---|---|---| | `devbox-palace` | `/home/developer/.mempalace` | MemPalace data — drawers, knowledge graph, embeddings. Treat as primary storage if you rely on agent memory. | | `devbox-chroma-cache` | `/home/developer/.cache/chroma` | ChromaDB embedding model cache (~80 MB; disposable, re-downloads in seconds) | ### Workspace bind mount `/workspace` is bind-mounted from `WORKSPACE_PATH` on the host (default `~/projects`). Source code never lives inside the container — your editor on the host and pi inside the container see the same files. ### SSH keys (read-only) `~/.ssh` is mounted read-only at `/home/developer/.ssh` for git push/pull. The container does **not** write to it. --- ## Configuration reference All config flows through `.env`. The full list (with annotations) is in [`.env.example`](https://gitea.jordbo.se/joakimp/pi-devbox/src/branch/main/.env.example). Here's the most relevant subset: | Variable | Default | Purpose | |---|---|---| | `WORKSPACE_PATH` | `~/projects` | Host path mounted as `/workspace` | | `SSH_KEY_PATH` | `~/.ssh` | Host path for SSH keys (read-only) | | `GIT_USER_NAME` | (empty) | Sets `git config --global user.name` inside container | | `GIT_USER_EMAIL` | (empty) | Sets `git config --global user.email` inside container | | `ANTHROPIC_API_KEY` | (unset) | Anthropic provider auth | | `OPENAI_API_KEY` | (unset) | OpenAI provider auth | | `GEMINI_API_KEY` | (unset) | Google Gemini auth | | `AWS_PROFILE` / `AWS_REGION` | (unset) | AWS Bedrock SSO flow | | `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` | (unset) | AWS Bedrock static creds | | `GITEA_ACCESS_TOKEN` / `GITEA_HOST` | (unset) | Gitea MCP server (optional) | | `GITHUB_PERSONAL_ACCESS_TOKEN` | (unset) | GitHub MCP server / git ops over HTTPS | | `DEVBOX_LAN_ACCESS` | `auto` | LAN-access mode: `auto` (jump only on VM-backed hosts), `jump`, `off` | | `HOST_SSH_USER` | (unset) | Host username for the LAN SSH jump (see opencode-devbox README) | | `LANG` / `LANGUAGE` / `LC_ALL` | `en_US.UTF-8` | Locale override | --- ## Versioning Tags follow the pi npm package version: `v0.74.0`, `v0.75.0`, … `latest` always points at the most recent successful release. Container-level rebuilds on the same pi version (security updates, base bumps, fixes) get a letter suffix: `v0.74.0b`, `v0.74.0c`, … The pi binary is inherited from `opencode-devbox:latest-pi-only`, so a release of this image must be preceded by an opencode-devbox release that bakes the target pi version into `latest-pi-only`. The smoke test enforces this (it asserts `pi --version` matches the tag). --- ## Building from source This image is a thin re-brand of the pi-only variant, so building it just pulls the base. To pin a specific pi-only build or hack on it: ```bash git clone https://gitea.jordbo.se/joakimp/pi-devbox cd pi-devbox # Default tracks latest-pi-only; override BASE_IMAGE to pin a build: docker compose build \ --build-arg BASE_IMAGE=joakimp/opencode-devbox:v1.15.13-pi-only docker compose up -d ``` To change the pi version, the pi extensions, or the install logic, edit `opencode-devbox/Dockerfile.variant` (the single source of truth) and release opencode-devbox — not this repo. Build args supported: | Arg | Default | Effect | |---|---|---| | `BASE_IMAGE` | `joakimp/opencode-devbox:latest-pi-only` | Parent image — set to a `:vX.Y.Z-pi-only` tag or a digest for reproducible builds | --- ## Troubleshooting **`pi --version` works but `pi` exits immediately.** First-run config probably hasn't been done. `docker compose exec -u developer devbox bash`, edit `~/.pi/agent/settings.json`, ensure a provider is set and the matching API key is exported. **AWS SSO token expired.** `aws sso login` from inside the container. The token cache is on the `~/.aws` bind-mount, so it persists; expiration is the issue. **Anthropic 401 / OpenAI 401.** Check the `.env` value made it in: `docker compose exec devbox env | grep ANTHROPIC` (etc). **`pi` prompts for an extension/MCP server you don't recognize.** Either toggle it off via `/ext` inside pi, or rename the file: `mv ~/.pi/agent/extensions/.ts{,.off}`. **Container won't start, error about `/workspace`.** `WORKSPACE_PATH` in `.env` doesn't exist on the host. Create the directory or fix the path. **Pi-toolkit symlinks lost after `docker compose down -v`.** That's expected — `-v` wipes named volumes. Don't do it unless you mean it. Container recreation without `-v` (the default) preserves all state. --- ## Related - **[opencode-devbox](https://gitea.jordbo.se/joakimp/opencode-devbox)** — the base image. Use this if you want both opencode and pi (it has a `latest-with-pi` variant) or just opencode. - **[pi-toolkit](https://gitea.jordbo.se/joakimp/pi-toolkit)** — keybindings, env loader, settings template. Cloned into `/opt/pi-toolkit` at image build time and `install.sh` runs on container start. - **[pi-extensions](https://gitea.jordbo.se/joakimp/pi-extensions)** — extension source. Same install pattern. - **[mempalace-toolkit](https://gitea.jordbo.se/joakimp/mempalace-toolkit)** — MemPalace bring-up. The `mempalace.ts` extension symlinked into `~/.pi/agent/extensions/` comes from here. - **[pi (upstream)](https://github.com/earendil-works/pi)** — the coding-agent itself. --- ## License MIT (this image and its source). Pi and the bundled tools each carry their own licenses.