A zero-configuration Docker Compose setup that mimics docker sandbox behavior.
This allows your Claude AI agents to run in a fully isolated environment.
One persistent container per workspace with automatic git configuration and SSH commit signing.
- Docker Sandbox Behavior: One container per workspace, reused across sessions
- Multiple Terminal Support: Open multiple tabs, each runs separate Claude instance
- Persistent State: Installed packages and files persist across sessions
- Zero Configuration: Automatically reads git config from your host system
- SSH Commit Signing: Auto-configured with your SSH agent (macOS or 1Password)
- Shared Filesystem: All terminals share the same workspace
- Python Variant: Optional Python image with uv, poetry, ruff, pytest, mypy
- cmux Notifications: Desktop notifications when Claude finishes a task (via cmux socket or OSC 777)
- Docker Desktop 4.50+ (for SSH agent forwarding)
- macOS or Linux
- Git configured on host (
git config --global user.nameanduser.email) - SSH keys available via SSH agent
cd ~/Sites/ai-sandbox
./ai-sandbox --build # builds and runs ai-sandbox:claude
./ai-sandbox python --build # builds and runs ai-sandbox:pythonOption A: Set in shell config (recommended):
# Add to ~/.zshrc or ~/.bashrc
export AI_SANDBOX_WORKSPACE_PATH=/your/workspace/path
alias ai-sandbox='~/Sites/ai-sandbox/ai-sandbox'Option B: Use .env file:
cp .env.example .env
# Edit .env and set AI_SANDBOX_WORKSPACE_PATHReload your shell:
source ~/.zshrc # or ~/.bashrcai-sandboxThat's it!
Just like docker sandbox, this setup maintains one container per workspace:
- First time: Creates a new container
- Subsequent runs: Reuses the existing container
- State persists: npm packages, files, changes all persist
Open multiple terminal tabs and run ai-sandbox in each:
# Terminal Tab 1
ai-sandbox
> "install express and create a server"
# Terminal Tab 2 (reuses same container)
ai-sandbox
> "create tests for the server"
# Can see express packages installed in Tab 1
# Terminal Tab 3
ai-sandbox
> "update the README"All tabs:
- Share the same filesystem
- Share installed packages
- Run separate Claude processes
- See each other's changes in real-time
# From any terminal
ai-sandboxThe script automatically:
- Creates container if it doesn't exist
- Reuses existing container if running
- Starts new Claude instance in that container
# Terminal 1
ai-sandbox
# Terminal 2 (new tab)
ai-sandbox # Connects to same container
# Terminal 3 (new tab)
ai-sandbox # Connects to same containerTo remove the container and start fresh:
cd ~/Sites/ai-sandbox
docker-compose downNext ai-sandbox command will create a new container.
Set via environment variable:
# Option 1: Set for single session
AI_SANDBOX_WORKSPACE_PATH=/your/workspace/path ai-sandbox
# Option 2: Set in your shell config (~/.zshrc)
export AI_SANDBOX_WORKSPACE_PATH=/your/workspace/path
# Option 3: Create .env file
cp .env.example .env
# Edit AI_SANDBOX_WORKSPACE_PATH in .envSet environment variable to use specific SSH key:
export AI_SANDBOX_SSH_KEY_NAME="[Docker Sandbox] GitHub"
ai-sandboxOverride the default container name (ai-sandbox) to run multiple independent sandboxes:
export AI_SANDBOX_CONTAINER_NAME="ai-sandbox-project-x"
ai-sandboxInject a Compose overlay file that is appended after all built-in overlays. Useful for wrapping ai-sandbox without modifying it:
export AI_SANDBOX_EXTRA_COMPOSE_FILES="/path/to/docker-compose.custom.yml"
ai-sandbox --buildGit config is automatically read from your ~/.gitconfig. To verify:
# Inside container
git config --global --listClaude authentication is stored in a Docker named volume (ai-sandbox-config) that persists across container restarts:
# On first run, authenticate via browser
ai-sandbox # Opens browser for OAuth authentication
# Subsequent runs use the persisted token
ai-sandbox # No authentication needed
# To reset authentication (force re-login)
docker volume rm ai-sandbox_ai-sandbox-configThe volume maps to /home/agent/.config inside the container, storing:
- Claude authentication tokens
- Claude settings and preferences
- Any other config files
Based on official docker/sandbox-templates:claude-code image with:
- Claude Code: Pre-installed from official image
- Languages: Python 3, Node.js, npm
- Version Control: git
- Build Tools: gcc, make, build-essential
- Databases: postgresql-client, sqlite3
- Network: curl, wget, socat, netcat
- Utilities: jq, vim, nano, tree, htop, zip, unzip
Get a desktop notification when Claude finishes a task or stops, using cmux.
Start the container with the cmux overlay:
CMUX_SOCKET_PATH=$CMUX_SOCKET_PATH \
CMUX_TAB_ID=$CMUX_TAB_ID \
docker compose -f docker-compose.yml -f docker-compose.ssh.yml -f docker-compose.cmux.yml upOr set the variables in your .env file:
CMUX_SOCKET_PATH=/path/to/cmux.sock
CMUX_TAB_ID=your-tab-idThe hooks/cmux-notify.sh script is triggered on Claude's Stop and Notification events (configured in hooks/claude-settings.json). It tries two delivery methods in order:
- Unix socket — sends a
notification.createJSON-RPC call to cmux via$CMUX_SOCKET_PATH - OSC 777 — falls back to terminal escape sequence (
\e]777;notify;title;body\a) if no socket is available
| File | Purpose |
|---|---|
docker-compose.cmux.yml |
Compose overlay: passes env vars and mounts the cmux socket |
hooks/cmux-notify.sh |
Notification hook script |
hooks/claude-settings.json |
Wires the hook to Claude's Stop/Notification events |
Copy hooks/claude-settings.json into the container's /home/agent/.claude/settings.json (the Dockerfile does this automatically) and cmux-notify.sh to /home/agent/.claude/hooks/cmux-notify.sh.
A Python-focused image is available that builds on top of ai-sandbox:claude and adds:
- uv — fast Python package manager
- poetry — dependency management
- ruff — linter/formatter
- pytest + pytest-cov — testing
- mypy — type checking
./ai-sandbox python --buildWorks automatically with both macOS native SSH agent and 1Password through Docker Desktop's /run/host-services/ssh-auth.sock.
Ensure your SSH keys are added to the keychain:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519- Enable SSH agent in 1Password settings
- Generate or import SSH keys in 1Password
- Optional: Set AI_SANDBOX_SSH_KEY_NAME environment variable
export AI_SANDBOX_SSH_KEY_NAME="[Docker Sandbox] GitHub"
Check SSH agent inside container:
docker exec ai-sandbox ssh-add -LShould list your SSH keys. If not:
- Ensure Docker Desktop SSH forwarding is enabled
- For 1Password: enable SSH agent in settings
- Restart Docker Desktop
Check inside container:
docker exec ai-sandbox git config --global --listShould show user.name and user.email. If not:
- Verify
git config --global user.nameworks on host - Check that
~/.gitconfigexists on host - Rebuild:
docker-compose build
If each ai-sandbox command creates a new container:
# Check for existing container
docker ps -a | grep ai-sandbox
# Remove old containers
docker rm -f ai-sandbox
# Try again
ai-sandboxIf Claude instances interfere with each other, they might be trying to modify the same files. This is expected behavior - coordinate work between terminals or use different directories.
- Base Image:
docker/sandbox-templates:claude-code - Container Model: One container per workspace (persists across sessions)
- User:
agent(non-root for security) - Workspace: Mounted at same path as host (docker sandbox behavior)
- SSH Agent: Forwarded from host via Docker Desktop
- Git Config: Read from host's
~/.gitconfigat startup - State: Persists in container until explicitly removed
ai-sandbox/
├── Dockerfile.claude # Base image (git config + SSH signing)
├── Dockerfile.python # Python variant (FROM ai-sandbox:claude)
├── docker-compose.yml # Base configuration
├── docker-compose.ssh.yml # SSH agent forwarding
├── docker-compose.python.yml # Python variant override
├── docker-compose.cmux.yml # cmux notification overlay
├── hooks/
│ ├── cmux-notify.sh # Notification hook (Stop + Notification events)
│ └── claude-settings.json # Claude hook wiring
├── ai-sandbox # Wrapper script (mimics docker sandbox)
├── .gitignore
└── README.md
- Container runs as non-root user (
agent) - Only workspace directory is mounted
- `${HOME}/.gitconfig mounted read-only (for .gitconfig only)
- SSH keys stay on host, only agent socket forwarded
- sudo limited to
chmodcommand only
Two environment variables let you wrap ai-sandbox without copying or modifying it.
Overrides the Docker container name (default: ai-sandbox). Use this when you need a distinct container so two sandboxes can coexist on the same host:
AI_SANDBOX_CONTAINER_NAME="ai-sandbox-custom" ai-sandboxInjects an additional Compose overlay file after all built-in overlays are assembled. The file is appended last, so it can override anything — image, container name, volumes, environment variables:
AI_SANDBOX_EXTRA_COMPOSE_FILES="/path/to/docker-compose.custom.yml" ai-sandbox --buildCombining both variables you can build a fully delegating wrapper in a few lines:
#!/usr/bin/env bash
# my-sandbox — delegates to ai-sandbox with a custom image and container name
MY_SANDBOX_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export MY_SANDBOX_DIR
export AI_SANDBOX_CONTAINER_NAME="ai-sandbox-custom"
export AI_SANDBOX_EXTRA_COMPOSE_FILES="${MY_SANDBOX_DIR}/docker-compose.custom.yml"
exec "${AI_SANDBOX_DIR:-${HOME}/Sites/ai-sandbox}/ai-sandbox" "$@"The Compose overlay (docker-compose.custom.yml) only needs to declare what differs from the base image:
services:
claude:
image: ai-sandbox:custom
build:
context: .
dockerfile: Dockerfile.custom
container_name: ai-sandbox-customWhen --build is passed, ai-sandbox builds ai-sandbox:claude first, then runs a second build pass with your overlay on top.
All arguments (--build, --with-spec-kit, etc.) are forwarded unchanged, so the wrapper is transparent to the caller.
After modifying the Dockerfile:
docker-compose down # Remove old container
docker-compose build # Rebuild image
ai-sandbox # Start with new imagePass additional environment variables:
# Edit docker-compose.ssh.yml and add:
environment:
- MY_VAR=valueMount additional directories by editing docker-compose.yml:
volumes:
- ${AI_SANDBOX_WORKSPACE_PATH}:${AI_SANDBOX_WORKSPACE_PATH}
- /path/to/other/dir:/mnt/other:ro-
One workspace, one container: The default container name is
ai-sandbox, so only one workspace can be active at a time. SetAI_SANDBOX_CONTAINER_NAMEto run multiple independent sandboxes simultaneously. -
Fresh start: To completely reset:
docker-compose down -v # Remove volumes too docker-compose build --no-cache ai-sandbox