- Shell 91.3%
- Dockerfile 8.7%
| AGENTS.md | ||
| Dockerfile | ||
| pid | ||
| README.md | ||
pid
pid runs the Pi coding agent in a locked-down rootless Docker sandbox while
keeping Pi state, its executable, and package caches in dedicated Docker
volumes. Pi home can instead use a host directory. The launcher and Dockerfile
are distributed through Git and update automatically after installation.
Requirements
The host needs:
- Linux with rootless Docker Engine 26 or newer
- The rootless Docker socket at
$XDG_RUNTIME_DIR/docker.sock - The Docker
systemdcgroup driver and seccomp enabled git,flock(normally fromutil-linux), andsha256sum~/.local/binonPATH
The launcher deliberately refuses to use a non-rootless Docker daemon.
Install
git clone https://git.tsbprodesk.co.uk/localuser/pid.git
cd pid
./pid --install
This clones the managed source into ~/.local/share/pid/source and creates:
~/.local/bin/pid -> ~/.local/share/pid/source/pid
Running pid --install again performs a clean reinstall. Installed launchers
fetch their configured Git branch before each invocation and accept only
fast-forward updates. A changed Dockerfile or AGENTS.md causes the image to
be refreshed on the next command that needs it.
Host-backed Pi home
Set PID_PI_HOME_DIR to use a host directory instead of the pid-pi-home
Docker volume. The directory is mounted read-write at /pi-home for every Pi,
shell, import, seed, and extension-update container:
export PID_PI_HOME_DIR="/home/localuser/Documents/syncthing/dotfiles/pid-pi-home"
pid
The path must be absolute and cannot contain commas or newlines. It is created
with mode 700 if it does not exist, and must be readable, writable, and
searchable by the current user. An empty directory is seeded on first launch;
an existing pid-pi-home Docker volume is not copied automatically. Put the
export in your shell profile to use it on every invocation.
A configured host directory takes precedence over PID_STATE_VOLUME. It is
never deleted by --rebuild, --reset-state, or --reset-all; those commands
still remove the applicable core and cache volumes. An explicit --rebuild
replaces installed npm and Git packages and removes loose global extensions,
but preserves the rest of the host-backed Pi home.
The directory contains credentials, sessions, installed packages, and other potentially sensitive data. When using Syncthing, share it only with trusted devices and avoid running Pi against the same synchronized home on multiple machines at once. Exit Pi and let Syncthing finish before switching machines.
Syncthing-managed Pi files
The three special Pi files can be supplied as absolute host paths. They are mounted read-write, so changes made by Pi are available to Syncthing:
export PID_SETTINGS_FILE="$HOME/Sync/pi/settings.json"
export PID_PROVIDERS_FILE="$HOME/Sync/pi/models.json"
export PID_AUTH_FILE="$HOME/Sync/pi/auth.json"
PID_MODELS_FILE is accepted as an alias for PID_PROVIDERS_FILE.
The files must already exist and be readable and writable. PID_AUTH_FILE
must also be owned by the current user and inaccessible to group/others. New
JSON files can be initialized with:
mkdir -p "$HOME/Sync/pi"
printf '{}\n' >"$HOME/Sync/pi/settings.json"
printf '{}\n' >"$HOME/Sync/pi/models.json"
printf '{}\n' >"$HOME/Sync/pi/auth.json"
chmod 600 "$HOME/Sync/pi/"*.json
Put the exports in your shell profile to apply them automatically. Package
registrations are seeded initially and replaced from the rebuilt image during
pid --rebuild. Other settings are preserved. Between rebuilds, packages can
be managed normally with pi install and pi remove.
Credential warning:
auth.jsoncontains reusable authentication tokens. Protect the Syncthing devices and transport, keep the file at mode 600, and do not commit it to Git. Pi, extensions, and commands running in the sandbox can read and update this file.
Docker binds each configured file directly. If Syncthing atomically replaces a
file while pid is already running, that container can retain the old inode.
Avoid simultaneous edits, or restart pid after a Syncthing update; every new
container binds the current file.
Without PID_AUTH_FILE, credentials can instead be copied once from the host's
normal Pi configuration into the isolated state volume:
pid --import-auth
This reads ${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/auth.json.
Commands
| Command | Description |
|---|---|
pid [PI arguments...] |
Update as needed and launch Pi in the current workspace. Arguments are forwarded to Pi. |
pid --install |
Clone/reinstall the configured Git distribution and repair the launcher symlink. Does not require Docker. |
pid --update |
Update the Git launcher, refresh the image if needed, update Pi core, and update extensions. Cannot be combined with either skip-update control. |
pid --skip-update [PI arguments...] |
Launch without Git self-update or Pi core/extension update checks. |
pid --shell |
Open Bash in the sandbox without performing Pi core or extension updates. Git self-update still runs unless combined as pid --skip-update --shell. |
pid --claude [Claude arguments...] |
Launch Claude Code instead of Pi in the same sandbox. See Claude Code. |
pid --rebuild |
Rebuild the image without cache, replace extensions, then remove the dedicated pid volumes. A configured PID_PI_HOME_DIR otherwise remains intact. |
pid --import-auth |
Copy the host Pi auth.json into the configured Pi home. |
pid --reset-state |
Remove volume-backed Pi home/state and the extension cache. A configured PID_PI_HOME_DIR is preserved. |
pid --reset-core |
Remove the persistent Pi executable and its update cache. |
pid --reset-all |
Remove the configured pid volumes and attached pid containers without rebuilding. A configured PID_PI_HOME_DIR is preserved. |
pid --doctor |
Print Docker security, version, paths, resource limits, volumes, and external-file configuration. |
pid --help |
Show concise command help. |
Wrapper commands are selected by the first argument. --skip-update is the
only prefix and must come first; for example, pid --skip-update --shell.
Maintenance commands such as --update do not accept trailing arguments.
Update controls
For a one-off fully offline-style launch that skips update checks:
pid --skip-update
The equivalent environment controls are separate:
PID_SKIP_SELF_UPDATE=1 pid # Skip only the Git launcher fetch
PID_SKIP_UPDATE=1 pid # Skip Pi core and extension update checks
Do not combine --update with --skip-update or PID_SKIP_UPDATE=1; the
launcher rejects contradictory update instructions.
PID_SKIP_UPDATE=1 still permits a required local image build when no image
exists or the checked-in Dockerfile or AGENTS.md changed.
Claude Code
pid --claude launches Claude Code in the same image with the same sandbox
restrictions and resource limits as Pi. Pi state, the Pi core, extension
caches, and the PID_SETTINGS_FILE/PID_PROVIDERS_FILE/PID_AUTH_FILE
mounts are not used. Remaining arguments are forwarded to claude:
pid --claude
pid --claude --continue
pid --skip-update --claude
- Claude's home (
~/.claude,~/.claude.json, credentials, sessions, and the Claude Code binary) is thepid-claude-homevolume, mounted at/claude-home. SetPID_CLAUDE_HOME_DIRto an absolute host directory to use that instead; it is created with mode 700 if missing. - On first launch Claude Code is installed with the official native installer
(
https://claude.ai/install.sh). Claude's own background auto-updater then keeps it current.--skip-updateorPID_SKIP_UPDATE=1setsDISABLE_AUTOUPDATER=1for the session. - Log in inside the container on first use; the login persists in Claude's
home. Host credentials and
ANTHROPIC_API_KEYare not forwarded. - The repository
AGENTS.mdis synchronised to~/.claude/CLAUDE.mdon every launch, overwriting local edits to that file. - Claude runs with
--dangerously-skip-permissions. Because the container user is root,IS_SANDBOX=1is set so Claude accepts that flag. Review projects before opening them withpid --claude.
--reset-all and --rebuild also delete the pid-claude-home volume
(logging Claude out and removing its sessions). A configured
PID_CLAUDE_HOME_DIR is preserved.
--rebuild data loss
pid --rebuild first completes a new image build. Only after a successful build
will it remove containers attached to, and then delete the configured pid
volumes. Without PID_PI_HOME_DIR, their default names are:
pid-pi-homepid-pi-corepid-pi-core-cachepid-pi-extension-cache
If the advanced PID_*_VOLUME variables are set, those resolved volume names
are deleted instead. When PID_PI_HOME_DIR is set, the state volume is not
created or deleted. Its npm and Git package directories are replaced from the
image, loose global extensions are removed, and settings.packages is reset to
the image package list. Credentials, sessions, and other settings are
preserved.
Without a host-backed Pi home, this deletes volume-held credentials, settings,
sessions, installed packages, and caches. Files supplied through
PID_SETTINGS_FILE, PID_PROVIDERS_FILE/PID_MODELS_FILE, and PID_AUTH_FILE
are host bind mounts
and are not deleted. The next launch recreates the volumes and seeds the bundled
extensions.
Environment variables
Distribution and Docker
| Variable | Default | Purpose |
|---|---|---|
PID_REPO_URL |
Project repository | Clone URL used by --install. |
PID_REPO_REF |
main |
Branch or tag used for installation and self-updates. The installer records it for later runs. |
PID_INSTALL_ROOT |
~/.local/share/pid |
Managed installation root. |
PID_IMAGE |
pid-sandbox:latest |
Docker image name. |
PID_IMAGE_DIR |
~/.local/share/pid |
Manual-install Docker build context fallback. A managed Git checkout is detected automatically. |
PID_DOCKER_HOST |
$XDG_RUNTIME_DIR/docker.sock |
Alternative rootless Docker endpoint, including the unix:// prefix. |
External Pi home and files
| Variable | Container destination |
|---|---|
PID_PI_HOME_DIR |
/pi-home (replaces the PID_STATE_VOLUME mount) |
PID_CLAUDE_HOME_DIR |
/claude-home for --claude (replaces the PID_CLAUDE_STATE_VOLUME mount) |
PID_SETTINGS_FILE |
/pi-home/agent/settings.json |
PID_PROVIDERS_FILE |
/pi-home/agent/models.json |
PID_MODELS_FILE |
Alias for PID_PROVIDERS_FILE |
PID_AUTH_FILE |
/pi-home/agent/auth.json |
The Pi home path must be an absolute directory without commas or newlines. The other paths must be absolute regular files with the same character restrictions and must be readable and writable. The auth file additionally requires current-user ownership and no group/other permission bits.
Resources and updates
| Variable | Default | Purpose |
|---|---|---|
PID_MEMORY |
12g |
Normal sandbox memory limit. |
PID_PIDS |
1024 |
Normal sandbox process limit. |
PID_TMP_SIZE |
2g |
Writable /tmp tmpfs size. |
PID_SHM_SIZE |
256m |
Shared-memory size. |
PID_UPDATE_MEMORY |
4g |
Core/extension updater memory limit. |
PID_SKIP_SELF_UPDATE |
0 |
Set to 1 to skip Git launcher updates. |
PID_SKIP_UPDATE |
0 |
Set to 1 to skip Pi core and extension updates and Claude Code auto-updates. |
Advanced volume names
These allow an independent set of persistent volumes. PID_STATE_VOLUME is
ignored when PID_PI_HOME_DIR is set.
| Variable | Default |
|---|---|
PID_STATE_VOLUME |
pid-pi-home |
PID_CORE_VOLUME |
pid-pi-core |
PID_CORE_CACHE_VOLUME |
pid-pi-core-cache |
PID_EXT_CACHE_VOLUME |
pid-pi-extension-cache |
PID_CLAUDE_STATE_VOLUME |
pid-claude-home |
Terminal integration
The launcher forwards these terminal capability variables when set:
TERM,COLORTERMTERM_PROGRAM,TERM_PROGRAM_VERSIONKITTY_WINDOW_ID,GHOSTTY_RESOURCES_DIRTMUX,STYPI_IMAGE_PROTOCOL,PI_TRUE_COLOR,PI_HYPERLINKSNO_COLOR,FORCE_COLOR
This preserves Kitty/Ghostty and Pi terminal detection without exposing host
GUI sockets. The launcher's status messages, help, and doctor report use a
compact color UI on interactive terminals. Output stays free of ANSI escapes
when redirected or when NO_COLOR is set; FORCE_COLOR=1 enables color
explicitly.
Image contents
The image includes Node.js 24, Pi, Git/Git LFS, GitHub CLI (gh), Python 3
(with python also pointing to Python 3), compilers/build tools, ripgrep, fd,
jq, ShellCheck, SQLite, network diagnostics, archive tools, and other common
command-line development utilities.
Every prepared Pi home receives the repository's AGENTS.md at
/pi-home/agent/AGENTS.md, where Pi loads it as global context. The launcher
resynchronizes this managed copy whenever the source file changes, including
for an existing Docker volume or host-backed Pi home.
The initial and post-rebuild package list is controlled by the image. Package
changes made with pi install or pi remove persist during ordinary Pi and
shell launches, until the next explicit rebuild. The image list contains:
pi-web-access@juicesharp/rpiv-ask-user-question@xynogen/pix-subagentpi-codex-goalpi-context-viewpi-token-speedpi-zentui@hk_net/pi-usage-bars@vanillagreen/pi-tool-renderer@paulpham157/apply-patch@gotgenes/pi-anthropic-auth
pi-math and xdg-utils are intentionally not installed.
Sandbox outline
- Rootless Docker only
- Read-only image filesystem
- All Linux capabilities dropped
no-new-privilegesand Docker's seccomp profile- Memory, process, core-dump, temporary-filesystem, and shared-memory limits
- The current workspace and explicitly configured Pi home, settings, models, and auth paths are bind-mounted from the host
- Pi core is read-only in the interactive agent container
- Persistent state uses either
PID_PI_HOME_DIRor a dedicated named volume; caches use dedicated named volumes
The launcher passes --approve to Pi, so project-local Pi configuration and
extensions in the mounted workspace are trusted automatically. Review projects
before opening them with pid.