Running AI Agents in Docker safely

Pi Agent — Docker Sandbox Guide (macOS)

Run Pi (pi.dev) per-project inside Docker. Pi can only touch your project folder — it has no access to your Mac’s root, home directory, or any other path you haven’t explicitly mounted.


What’s in this folder

pi-docker/
├── Dockerfile   — The sandbox image definition
├── build.sh     — One-time image build script
├── run.sh       — Per-project launcher
└── GUIDE.md     — This file

Prerequisites

  1. Docker Desktop for Mac — download from https://docker.com/products/docker-desktop Install it, open it, and make sure the whale icon appears in your menu bar.

  2. An API key for your LLM provider (Anthropic, OpenAI, OpenRouter, etc.) Add it to your shell profile (~/.zshrc) so run.sh picks it up automatically:

    # ~/.zshrc
    export ANTHROPIC_API_KEY="sk-ant-..."
    # or
    export OPENAI_API_KEY="sk-..."
    # or
    export OPENROUTER_API_KEY="sk-or-..."
    

    Then reload: source ~/.zshrc


One-time setup

1. Put the pi-docker folder somewhere permanent

mv pi-docker ~/pi-docker

2. Make the scripts executable

chmod +x ~/pi-docker/build.sh ~/pi-docker/run.sh

3. Build the Docker image (once)

~/pi-docker/build.sh

This downloads Node 22 and installs Pi inside the image. Takes ~1 minute. You only need to run this once (or after updating Dockerfile).


Using Pi in a project

Navigate to any project and launch Pi:

cd ~/projects/my-app
~/pi-docker/run.sh

Inside the container, Pi sees:

/workspace/        ← your project (read + write)
/home/agent/.pi/   ← your Pi config, sessions, API keys (persisted)

Everything else on your Mac is invisible.


Convenience: shell alias

Add this to ~/.zshrc so you can just type pi from any project:

alias pi='~/pi-docker/run.sh'

Then reload: source ~/.zshrc

Now:

cd ~/projects/my-app
pi

Referencing files from another project

By default Pi can only see the current project. To give it access to another project, edit run.sh and uncomment the EXTRA_MOUNTS block:

# run.sh — EXTRA_MOUNTS section
EXTRA_MOUNTS+=("-v" "$HOME/projects/shared-lib:/workspace/shared-lib:ro")

Multiple mounts:

EXTRA_MOUNTS+=("-v" "$HOME/projects/shared-lib:/workspace/shared-lib:ro")
EXTRA_MOUNTS+=("-v" "$HOME/projects/design-tokens:/workspace/design-tokens:ro")

Inside Pi, reference those files normally:

"check /workspace/shared-lib/src/utils.ts for the helper function"

If each project needs different mounts, copy run.sh into the project root and customize the EXTRA_MOUNTS for that project:

cp ~/pi-docker/run.sh ~/projects/my-app/pi.sh
chmod +x ~/projects/my-app/pi.sh
# edit my-app/pi.sh → add its specific extra mounts

Then run Pi from that project with:

cd ~/projects/my-app
./pi.sh

Pi config & sessions

Pi’s global config (~/.pi/) is mounted from your Mac into every container. This means:

To set a default provider/model for all projects, create or edit ~/.pi/agent/settings.json on your Mac:

{
  "defaultProvider": "anthropic",
  "defaultModel": "claude-sonnet-4-20250514"
}

To override settings for one project only, create .pi/settings.json inside that project:

{
  "defaultProvider": "openai",
  "defaultModel": "gpt-4o"
}

Updating Pi

Since Pi is baked into the Docker image, update it by rebuilding:

~/pi-docker/build.sh

This pulls the latest @earendil-works/pi-coding-agent from npm.


Security summary

ProtectionHow
No host root accessContainer runs as non-root agent user
No kernel privilege escalation--cap-drop=ALL + --security-opt no-new-privileges
Filesystem isolationOnly explicitly mounted paths are visible
No leftover containers--rm removes the container on exit
Per-project isolationEach docker run is a fresh container

Troubleshooting

”Docker is not running” → Open Docker Desktop from Applications.

”Image not found” → Run ~/pi-docker/build.sh first.

Pi can’t find my API key → Make sure ANTHROPIC_API_KEY (or equivalent) is exported in ~/.zshrc and you’ve run source ~/.zshrc in the current terminal.

Pi can’t see a file from another project → Add a mount for it in the EXTRA_MOUNTS section of run.sh. Paths must be absolute (use $HOME/... not ~/...).

Changes to files aren’t visible on my Mac → The mount is two-way by default. If Pi wrote a file, it’s already on your Mac at the same relative path inside your project folder.

© 2026 AW

Instagram 𝕏 GitHub