Running Pi in a Docker Container
Run pi inside an isolated Docker container to sandbox the agent's access to your filesystem. The container ensures pi can only read/write your mounted project directory and pi settings — everything else on your host stays protected.
Prerequisites
- Docker installed and running on your host
- A project directory you want pi to work in
- API credentials for your LLM provider (Vertex AI, Gemini, etc.)
- A GitHub token (
GITHUB_TOKEN) for GitHub operations
Quick start
docker pull ghcr.io/myk-org/pi-config:latest
docker run --rm -it \
--name "pi-config-$(basename $PWD)-$(date +%s)" \
--network host \
-v "$PWD":"$PWD":rw \
-v "$HOME/.pi":"$HOME/.pi":rw \
-v "$HOME/.gitconfig":"$HOME/.gitconfig":ro \
-v "$HOME/.gitignore-global":"$HOME/.gitignore-global":ro \
-v "$HOME/.ssh":"$HOME/.ssh":ro \
-v "$HOME/.config/gh":"$HOME/.config/gh":ro \
-v /tmp/pi-work:/tmp/pi-work:rw \
-w "$PWD" \
ghcr.io/myk-org/pi-config:latest
This starts an interactive pi session in your current project directory. The container is destroyed when you exit (--rm).
Step 1: Create an environment file
Create ~/.pi/.env with your credentials and container-specific settings:
# Timezone (for correct timestamps)
TZ=America/New_York
# Host username — required when your host user is not "node"
PI_HOST_USER=youruser
# Google Cloud / Vertex AI
GOOGLE_CLOUD_PROJECT=your-project-id
GOOGLE_CLOUD_LOCATION=us-east5
GOOGLE_APPLICATION_CREDENTIALS=/home/youruser/.config/gcloud/application_default_credentials.json
VERTEX_PROJECT_ID=your-project-id
VERTEX_REGION=us-east5
VERTEX_CLAUDE_1M=true
# GitHub
GITHUB_TOKEN=ghp_xxx
GITHUB_API_TOKEN=ghp_xxx
GH_CONFIG_DIR=/home/youruser/.config/gh
# Gemini (optional — enables image generation)
GEMINI_API_KEY=xxx
PI_IMAGE_MODEL=gemini-3-pro-image
Warning: Paths in the environment file (like
GOOGLE_APPLICATION_CREDENTIALS) must use your container home path (/home/<PI_HOST_USER>/...), not the host path. ThePI_HOST_USERmechanism creates symlinks so these paths resolve correctly inside the container.
Step 2: Run with the environment file
docker run --rm -it \
--name "pi-config-$(basename $PWD)-$(date +%s)" \
--network host \
--env-file "$HOME/.pi/.env" \
-v "$PWD":"$PWD":rw \
-v "$HOME/.pi":"$HOME/.pi":rw \
-v "$HOME/.gitconfig":"$HOME/.gitconfig":ro \
-v "$HOME/.gitignore-global":"$HOME/.gitignore-global":ro \
-v "$HOME/.ssh":"$HOME/.ssh":ro \
-v "$HOME/.config/gh":"$HOME/.config/gh":ro \
-v /tmp/pi-work:/tmp/pi-work:rw \
-w "$PWD" \
ghcr.io/myk-org/pi-config:latest
Step 3: Set up a shell alias
Add this to your ~/.bashrc or ~/.zshrc to start pi from any project directory:
alias pi-docker='docker pull ghcr.io/myk-org/pi-config:latest && \
docker run --rm -it \
--name "pi-config-$(basename $PWD)-$(date +%s)" \
--network host \
--env-file "$HOME/.pi/.env" \
-v "$PWD":"$PWD":rw \
-v "$HOME/.pi":"$HOME/.pi":rw \
-v "$HOME/.gitconfig":"$HOME/.gitconfig":ro \
-v "$HOME/.gitignore-global":"$HOME/.gitignore-global":ro \
-v "$HOME/.ssh":"$HOME/.ssh":ro \
-v "$HOME/.config/gh":"$HOME/.config/gh":ro \
-v /tmp/pi-work:/tmp/pi-work:rw \
-w "$PWD" \
ghcr.io/myk-org/pi-config:latest'
Then run from any project:
cd ~/projects/my-app
pi-docker
Tip: The alias pulls the latest image on every run. If you prefer faster startup, remove the
docker pullline and update manually withdocker pull ghcr.io/myk-org/pi-config:latest.
Understanding the volume mounts
Every mount serves a specific purpose:
| Mount | Mode | Purpose |
|---|---|---|
$PWD:$PWD |
rw | Your project directory — the only writable workspace |
$HOME/.pi:$HOME/.pi |
rw | Pi settings, sessions, memory, and installed packages |
$HOME/.gitconfig:$HOME/.gitconfig |
ro | Git configuration (user name, email, aliases) |
$HOME/.gitignore-global:$HOME/.gitignore-global |
ro | Global gitignore patterns |
$HOME/.ssh:$HOME/.ssh |
ro | SSH keys for git push/pull |
$HOME/.config/gh:$HOME/.config/gh |
ro | GitHub CLI authentication |
/tmp/pi-work:/tmp/pi-work |
rw | Temp files that persist across container restarts |
Note: Read-only (
:ro) mounts prevent the agent from modifying your host configuration. The container automatically copies.gitconfigto a writable location internally so git operations still work.
How PI_HOST_USER works
The container runs as user node (UID 1000) with home directory /home/node. When you mount host paths that expand to /home/youruser/..., they won't resolve inside the container without help.
Setting PI_HOST_USER=youruser tells the init script to:
- Create symlinks between
/home/youruserand/home/node - Set
HOMEto/home/youruserinside the container - Ensure all mounted paths resolve correctly
If you skip this variable, mounts that use $HOME expansion may not resolve inside the container.
Filesystem isolation
The container enforces strict filesystem boundaries:
- Read-write: Your project directory (
$PWD) and pi settings (~/.pi) - Read-only: Git, GitHub, and SSH configuration
- Blocked: All other host directories, other git repos, system files
This means the agent cannot accidentally modify files outside your project or access sensitive data on your host.
Network mode
The --network host flag shares your host's network stack with the container. This is required for:
- Local MCP servers (via
mcpl) - LiteLLM proxy
- File preview (agents serve generated HTML via a local HTTP server)
- Pidash and Pidiff web dashboards
If you only use cloud-based LLM providers and don't need local services, you can omit --network host.
Advanced Usage
Optional mounts
Add these mounts to enable additional features:
| Mount | Mode | Purpose |
|---|---|---|
$HOME/.config/gcloud/application_default_credentials.json (same path) |
ro | Google Cloud ADC for Claude via Vertex AI |
$HOME/.config/mcpl/mcp.json (same path) |
ro | MCP server configuration for mcpl |
$HOME/.agents:$HOME/.agents |
rw | User-level skills |
$HOME/.config/cursor/auth.json (same path) |
ro | Cursor CLI auth for acpx models |
$HOME/.config/glab-cli:$HOME/.config/glab-cli |
ro | GitLab CLI config |
$HOME/.coderabbit:$HOME/.coderabbit |
rw | CodeRabbit CLI auth and review data |
$HOME/screenshots:$HOME/screenshots |
ro | Share screenshots/images with the agent |
/var/run/docker.sock:/var/run/docker.sock |
ro | Docker container inspection via docker-safe |
/var/run/podman/podman.sock:/var/run/podman/podman.sock |
ro | Podman container inspection via docker-safe |
When using mcpl, add the config path to your .env file too:
MCPL_CONFIG_FILES=/home/youruser/.config/mcpl/mcp.json
Docker socket access
To let the agent inspect running containers (read-only), mount the Docker socket:
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--group-add $(stat -c '%g' /var/run/docker.sock)
The init script automatically handles socket permissions — it detects the socket's group ID and either adds the container user to that group or sets an ACL.
The agent uses a restricted docker-safe wrapper that only allows read-only commands:
| Allowed | Blocked |
|---|---|
ps, logs, inspect, top, stats |
exec, run, rm, cp |
port, diff, images, version, info |
build, push, pull, stop, kill |
For Podman, mount the Podman socket instead and set the runtime:
-v /var/run/podman/podman.sock:/var/run/podman/podman.sock:ro
Dashboard configuration
Two web dashboards start automatically alongside your pi session. See Using the Web Dashboard and Diff Viewer for full details on using them.
| Dashboard | Default Port | Environment Variable | URL |
|---|---|---|---|
| Pidash | 19190 | PI_PIDASH_PORT |
http://localhost:19190 |
| Pidiff | 19290 | PI_PIDIFF_PORT |
http://localhost:19290 |
To use custom ports or disable either dashboard, add to your .env:
# Custom ports
PI_PIDASH_PORT=9999
PI_PIDIFF_PORT=9998
# Or disable entirely
PI_PIDASH_ENABLE=false
PI_PIDIFF_ENABLE=false
External agent providers (acpx)
To route prompts through external AI agents like Cursor, set ACPX_AGENTS in your .env and mount the auth file:
ACPX_AGENTS=cursor
-v "$HOME/.config/cursor/auth.json":"$HOME/.config/cursor/auth.json":ro
See Using External AI Agents (Cursor, Claude, Gemini) for setup details.
Passing arguments to pi
Any arguments after the image name are forwarded to pi:
# Run a specific slash command
docker run --rm -it ... ghcr.io/myk-org/pi-config:latest /implement add retry logic
# Start with a prompt
docker run --rm -it ... ghcr.io/myk-org/pi-config:latest "fix the failing tests"
Building from source
git clone https://github.com/myk-org/pi-config.git
cd pi-config
docker build -t ghcr.io/myk-org/pi-config:latest .
Note: The image is built for linux/amd64 only. On ARM hosts, build with
--platform linux/amd64.
Project settings inside the container
Per-project settings in .pi/pi-config-settings.json override environment variables for that project. These work identically inside and outside the container:
{
"co_author": true,
"use_worktrees": false,
"dream_interval_hours": 3
}
See Configuration and Environment Variables Reference for all available settings.
Complete alias with all optional mounts
Here's a full alias including every optional mount:
alias pi-docker='docker pull ghcr.io/myk-org/pi-config:latest && \
docker run --rm -it \
--name "pi-config-$(basename $PWD)-$(date +%s)" \
--network host \
--env-file "$HOME/.pi/.env" \
-v "$PWD":"$PWD":rw \
-v "$HOME/.pi":"$HOME/.pi":rw \
-v "$HOME/.gitconfig":"$HOME/.gitconfig":ro \
-v "$HOME/.gitignore-global":"$HOME/.gitignore-global":ro \
-v "$HOME/.ssh":"$HOME/.ssh":ro \
-v "$HOME/.config/gh":"$HOME/.config/gh":ro \
-v "$HOME/.config/mcpl/mcp.json":"$HOME/.config/mcpl/mcp.json":ro \
-v "$HOME/.agents":"$HOME/.agents":rw \
-v "$HOME/.config/gcloud/application_default_credentials.json":"$HOME/.config/gcloud/application_default_credentials.json":ro \
-v "$HOME/.config/cursor/auth.json":"$HOME/.config/cursor/auth.json":ro \
-v "$HOME/.config/glab-cli":"$HOME/.config/glab-cli":ro \
-v "$HOME/.coderabbit":"$HOME/.coderabbit":rw \
-v "$HOME/screenshots":"$HOME/screenshots":ro \
-v /tmp/pi-work:/tmp/pi-work:rw \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--group-add $(stat -c '%g' /var/run/docker.sock) \
-w "$PWD" \
ghcr.io/myk-org/pi-config:latest'
Environment variables reference
These are the container-specific variables you can set in your .env file. For the complete environment variables reference, see Configuration and Environment Variables Reference.
| Variable | Required | Description |
|---|---|---|
PI_HOST_USER |
Yes (if your username ≠ node) |
Maps host home directory into container |
TZ |
No | Timezone for timestamps (e.g., America/New_York) |
GOOGLE_CLOUD_PROJECT |
Provider-dependent | Google Cloud project ID |
GOOGLE_CLOUD_LOCATION |
Provider-dependent | Google Cloud region |
GOOGLE_APPLICATION_CREDENTIALS |
Provider-dependent | Path to ADC JSON (container path) |
VERTEX_PROJECT_ID |
Provider-dependent | Vertex AI project |
VERTEX_REGION |
Provider-dependent | Vertex AI region |
VERTEX_CLAUDE_1M |
No | Enable 1M context for Claude on Vertex |
GITHUB_TOKEN |
Yes | GitHub personal access token |
GITHUB_API_TOKEN |
Yes | GitHub API token (can be same as above) |
GH_CONFIG_DIR |
No | GitHub CLI config directory (container path) |
GEMINI_API_KEY |
No | Gemini API key (for image generation) |
PI_IMAGE_MODEL |
No | Gemini model for image generation |
ACPX_AGENTS |
No | Comma-separated acpx agents (e.g., cursor) |
PI_PIDASH_PORT |
No | Pidash dashboard port (default: 19190) |
PI_PIDIFF_PORT |
No | Pidiff diff viewer port (default: 19290) |
PI_PIDASH_ENABLE |
No | Set false to disable pidash |
PI_PIDIFF_ENABLE |
No | Set false to disable pidiff |
PI_CO_AUTHOR |
No | Add co-author trailer to commits |
PI_USE_WORKTREES |
No | Force git worktree workflow |
PI_DREAM_INTERVAL_HOURS |
No | Memory dreaming interval (default: 3) |
MCPL_CONFIG_FILES |
No | Path to MCP Launchpad config (container path) |
Pre-installed tools
The container image comes with all tools ready to use:
| Tool | Purpose |
|---|---|
git, gh, glab |
Version control, GitHub CLI, GitLab CLI |
uv / uvx |
Python package management and execution |
go |
Go development |
node / npm |
JavaScript runtime (Node.js 22) |
bun |
Fast JavaScript runtime (required by coms-net) |
kubectl / oc |
Kubernetes and OpenShift CLI |
mcpl |
MCP server access |
acpx |
Agent proxy for external AI models |
agent-browser |
Browser automation (Chromium via Playwright) |
docker-safe |
Read-only Docker/Podman inspection wrapper |
cr |
CodeRabbit CLI for local AI code reviews |
prek |
Pre-commit hook runner |
mcp-proxy |
MCP transport proxy |
cursor, claude |
External AI agent CLIs |
jq, curl |
JSON processing, HTTP requests |
Container startup lifecycle
On every container start, the entrypoint automatically:
- Installs/updates pi to the latest version
- Installs or updates the
pi-configpackage from GitHub - Installs
myk-pi-toolsCLI from the latest source - Registers companion packages (
pi-web-access,pi-tasks) - Copies
.gitconfigto a writable location (since the mount is read-only) - Configures SSH timeouts for git operations (15s keepalive, 10s connect timeout)
- Ensures
.pi/memory/,.worktrees/, and.pi/tasks/are in the global gitignore - Starts pi with any arguments you passed
This means you always get the latest version of pi and all extensions on every session.
Troubleshooting
Mounted paths don't resolve inside the container
Make sure PI_HOST_USER in your .env file matches your host username exactly. Without it, paths like /home/youruser/.ssh won't resolve because the container's default home is /home/node.
Permission denied on mounted files
The container runs as user node (UID 1000). If your host files are owned by a different UID, you may see permission errors. Ensure your host user's UID is 1000, or adjust file permissions on the host.
Container can't reach local services
Make sure you included --network host in your docker run command. Without it, the container has its own network namespace and can't reach services on localhost.
Git push/pull hangs
The container sets SSH keepalive and connection timeouts automatically (15-second keepalive interval, 10-second connection timeout). If git operations still hang, check that your SSH keys are correctly mounted at $HOME/.ssh with :ro mode.
Startup WARNING about cached packages
A WARNING on stderr during startup is normal when pi-config is already cached in ~/.pi. The container runs pi install and pi update on every start to stay current. If pi misbehaves, verify your network connectivity.