pi-docker-sandbox
Runs the pi coding agent only inside a hardened Docker container. pi has no permission prompts, so this container is the security boundary: it sees exactly one project, the read-only agent config, the memory of that project’s domain and its own state volume — nothing else from the host.
The agent config itself lives in pi-config. Which host path appears
where in the container is defined in docs/path-contract.md, the
single contract both repos follow.
Prerequisites#
- macOS with Docker Desktop (or OrbStack) running —
docker infomust work ~/pi/containingpi-config/,pi-docker-sandbox/,brain/pi-memory-{core,private,work}/andws/{private,work}/(the default domains; more withpi-sandbox domain add <name>)- Task runner and checks:
brew install just bats-core shellcheck gitleaks(jqships with macOS)
First-time setup#
cd ~/pi/pi-docker-sandbox
cp .env.example .env # fill GIT_AUTHOR_* (and BRAIN_*_URL for memory sync)
export PATH="$PWD/bin:$PATH" # or add this to your shell profile
pi-sandbox rebuild # builds pi-sandbox:<version>
pi-sandbox start ~/pi/ws/private/<repo>
# inside pi, once: /login.env holds no secrets; credentials are explained in docs/credentials.md.
Daily use#
| Task | Command |
|---|---|
| Start a session | pi-sandbox start ~/pi/ws/<domain>/<repo> |
| Continue the last one | pi-sandbox start <path> -- --continue |
| Also read another domain’s memory (read-only, this run) | pi-sandbox start <path> --with-domain <name> |
| Add a domain (workspace folder + memory store) | pi-sandbox domain add <name> or just domain-add <name> |
| Show the domains | pi-sandbox domain list |
| Settings, MCP servers, instructions, skills and prompt templates per domain / customer / project | files in config/pi-config-<domain>/… (docs/config.md); check with pi-sandbox config show <path> |
| Session history per domain (kept 30 days by default) | pi-sandbox sessions list, sessions prune <domain>|--all, sessions clear <domain> |
| Use a Keychain API key instead of the stored login | pi-sandbox start <path> --key-from-keychain |
| Shell in the same container | pi-sandbox shell <path> |
| Check the isolation | pi-sandbox selftest <path> |
| Stop sessions | pi-sandbox stop <path> or pi-sandbox stop --all |
| Rebuild the image | pi-sandbox rebuild [--no-cache] |
| Review and push memory | pi-sandbox memory-sync <domain> |
| Clone or refresh whole forge groups into the workspace | pi-sandbox workspace clone <domain> <host>/<group> (docs/workspace.md) |
| Check memory (read-only) | pi-sandbox memory-check <domain> |
All commands, flags, exit codes and warnings: docs/commands.md. Adding a project: docs/onboarding.md.
Repo layout#
Dockerfile image: pinned base digest + pinned pi, user pi (uid 1000)
compose.yaml session container: mounts, env, hardening, limits
justfile task runner: `just check`, `just gate` (run `just` to list)
bin/pi-sandbox the host command (bash 3.2 compatible)
container/entrypoint.sh prepares agent state from the read-only config, runs pi
empty/ empty placeholder for a missing memory store
scripts/ `just check` helpers (definition and docs checks)
tests/ bats suites, stubs, and the in-container isolation test
docs/ path contract, commands, credentials, security, state, internals
.env.example names of every local setting (copy to .env, git-ignored)Documentation#
| Doc | Content |
|---|---|
| docs/path-contract.md | Every mount and env var, host path → container path |
| docs/commands.md | Commands, flags, exit codes, warnings, just recipes |
| docs/credentials.md | Login vs. Keychain key, precedence, revoke and replace |
| docs/security.md | Threat model, hardening, network endpoints, accepted risks |
| docs/state.md | The pi-agent-state volume, shared-history risks, deleting |
| docs/workspace.md | Cloning whole forge groups with ghorg: commands, layout, tokens, refresh rules |
| docs/memory.md | Memory scope per session, session context, memory-sync review flow, memory check, sync credentials |
| docs/internals.md | What each script and compose file does |
| docs/release.md | semantic-release, the released image, runner and secrets setup |
| docs/dependencies.md | Renovate: what is updated, commit types, PR checks, setup |
Version bumps#
Versions change only deliberately (spec FR-009):
| What | Pinned in |
|---|---|
| pi | Dockerfile → ARG PI_VERSION |
| Base image | Dockerfile → FROM node:24-bookworm-slim@sha256:… (resolve with docker buildx imagetools inspect node:24-bookworm-slim) |
| pi packages | pi-config/agent/settings.json → packages (exact @x.y.z) |
Renovate proposes all of these as pull requests (docs/dependencies.md).
To bump by hand: change the pin → pi-sandbox rebuild → just gate → commit as
build(deps): bump <component> to <version>. On main, that commit makes semantic-release cut
a patch release and publish a new image (docs/release.md).
Releases#
Every push to main runs .forgejo/workflows/release.yml on the self-hosted runner: just check, then semantic-release tags v<version>, updates CHANGELOG.md and VERSION, pushes
codeberg.org/black0ut-pi-setup/pi-sandbox:<version> (amd64 + arm64) and creates the Codeberg
release. Before each image push, older images are pruned so the Codeberg package quota holds
(the new and the previous release stay; docs/release.md).
pi-sandbox uses the image matching VERSION and pulls it on every start; it warns when this
checkout is not on main, because VERSION is then likely stale
(docs/commands.md).
Quality gates#
Before every commit to main (constitution gates):
just check # shellcheck, bats, compose lint, sandbox definition check, docs check
just gate # rebuild + in-container selftest with the pinned pi versionjust quickstart runs the whole automated quickstart validation (isolation, warm start,
config, refusals, gates, parallel sessions) and then lists the steps that need you.
In this part
- Onboarding
- Onboarding a project
- Commands
- pi-sandbox commands
- Credentials
- Workspace cloning
- Workspace cloning (ghorg)