A Docker-based sandbox environment that containerises Claude Code with a pre-configured development toolchain. This project provides isolated, reproducible execution of Claude Code with integrated Google Vertex AI support for model access.
Claude Sandbox encapsulates Claude Code and its dependencies in a Docker container, eliminating environment setup complexity. It runs as a non-root user with secure credential management, persistent session support, and seamless git integration.
Key features:
- Containerised environment — Consistent toolchain across all executions
- Session persistence — Named containers resume where you left off
- Credential isolation — Read-only mounting of sensitive credentials (Google Cloud, SSH keys)
- Smart mount strategy — Automatically detects git repositories and mounts intelligently
- SSH agent relay — Use password-protected SSH keys securely in containers without exposing passphrases
- Secure SSH for git — SSH keys mounted read-only with optional passphrase support via relay
- State preservation — Claude memory and todo lists persist across sessions
- Non-root execution — Runs as unprivileged user for enhanced security
- Health checks — Built-in container health verification
- Docker (with Daemon running) or Podman
- Bash shell
- Google Application Default Credentials (optional, for Vertex AI)
- SSH keys configured for GitHub (optional, for git SSH operations)
Using Docker:
docker build -f docker/Dockerfile -t claude_sandbox .Using Podman:
podman build -f docker/Dockerfile -t claude_sandbox .Docker and Podman maintain separate image registries. An image built with Docker is not visible to Podman, and vice versa.
You must build and run with the same container runtime:
# Option A: Use Docker for both build and run
docker build -f docker/Dockerfile -t claude_sandbox .
MODEL=haiku ./claude-sandbox.sh
# Option B: Use Podman for both build and run
podman build -f docker/Dockerfile -t claude_sandbox .
MODEL=haiku CONTAINER_RUNTIME=podman ./claude-sandbox.shDo not mix runtimes (e.g., Docker build with Podman run) — the container runtime will not find the image and will attempt to pull from a remote registry, resulting in an error.
Two environment variables are required:
MODEL=haiku KEYFILE=~/.ssh/id_ed25519 ./claude-sandbox.shRequired variables:
MODEL— The Claude model to use (haiku,sonnet,opus, or any valid Claude model identifier)KEYFILE— The SSH key file to use for git operations (e.g.,~/.ssh/id_ed25519or~/.ssh/id_rsa)
The script automatically:
- Detects whether you're in a git repository
- Mounts your project at an appropriate location
- Mounts the SSH key specified by
KEYFILEfor git operations - Passes Google Cloud credentials for Vertex AI access
- Connects to the SSH agent relay (if running) for passphrase-protected keys
- Resumes previous sessions or starts a new one
- Sets the editor to vim
- Runs the container in a tmux session for easy reconnection
Each time you run the script, the container starts in a named tmux session. This allows you to detach and reconnect without restarting the container.
Session naming format: {runtime}-{container-name}
Examples:
docker-claude-sandbox-claude(Docker + sandbox-claude repo)podman-claude-sandbox-claude(Podman + sandbox-claude repo)
Workflow:
# First run: Creates tmux session and attaches
MODEL=haiku KEYFILE=~/.ssh/id_ed25519 ./claude-sandbox.sh
# In the container, work as usual. When you want to exit:
# Press Ctrl+B then D to detach (keeps container running)
# Later, reconnect to the running session:
# Option 1: Run the same command again
MODEL=haiku KEYFILE=~/.ssh/id_ed25519 ./claude-sandbox.sh
# Option 2: Attach directly using tmux
tmux attach -t docker-claude-sandbox-claude
# List all sessions
tmux list-sessions
# Kill a session (stops the container)
tmux kill-session -t docker-claude-sandbox-claudeBenefits:
- Container keeps running after you exit
- No need to rebuild or restart the container
- Multiple containers can run simultaneously in different sessions
- Works with both Docker and Podman
If your SSH key has a passphrase, you can use it in containers without removing the passphrase:
One-time setup per session:
-
Start the SSH agent relay in a tmux session, specifying the SSH key:
KEYFILE=~/.ssh/id_ed25519 tmux new-session -d -s ssh-relay './start-ssh-agent-relay.sh'
The relay script automatically caches your passphrase and starts the relay on port 6010.
-
Start Claude Code with the same SSH key:
MODEL=haiku KEYFILE=~/.ssh/id_ed25519 ./claude-sandbox.sh
Inside the container, SSH operations work without prompting for a passphrase:
ssh -T git@github.com
git clone git@github.com:user/repo.gitHow it works: Your passphrase is cached on the host machine (never enters the container). The relay bridges your host's ssh-agent to the container via TCP, allowing containers to request signed operations without ever seeing the passphrase.
Manage the relay:
# View relay output
tmux attach -t ssh-relay
# Detach without stopping the relay (Ctrl+B then D)
# Stop the relay when done
tmux kill-session -t ssh-relayOptional: Auto-start relay in Ghostty
If you use Ghostty, you can auto-start the relay when opening a new terminal by adding this to ~/.config/ghostty/config:
initial-command = tmux new-session -d -s ssh-relay '/path/to/sandbox-claude/start-ssh-agent-relay.sh'Replace /path/to/sandbox-claude with the actual path to your repository. This starts the relay automatically in each new terminal session.
To use Podman as your container runtime:
MODEL=haiku KEYFILE=~/.ssh/id_ed25519 CONTAINER_RUNTIME=podman ./claude-sandbox.shSSH git operations work seamlessly with Podman. Your SSH key specified by KEYFILE is mounted read-only into the container, enabling native git SSH operations (e.g., git clone git@github.com:..., git push, etc.) for any repository.
The script intelligently determines how to mount your workspace based on whether you're in a git repository.
In a git repository:
- Repository root mounts at
/home/agent/<repo-name> - Relative paths are preserved in the container
- Container is named
claude-<repo-name>for easy identification - Example: In
/home/user/projects/my-app, the repository mounts at/home/agent/my-app
Outside a git repository:
- Current directory mounts at
/home/agent/workspace - Container is named
claude-workspace-<hash>based on directory path hash
When the container starts, the working directory is automatically set based on your location:
In a repository:
- If you run the script from the repo root, you start in
/home/agent/<repo-name> - If you run it from a subdirectory (e.g.,
src/), you start in/home/agent/<repo-name>/src - Relative paths are preserved, so
cd ../../works as expected
Outside a repository:
- You always start in
/home/agent/workspace
Credentials are securely mounted with careful attention to read/write permissions:
| Credential | Host Location | Container Path | Purpose | Read-Only |
|---|---|---|---|---|
| Google Cloud ADC | ~/.config/gcloud/application_default_credentials.json |
<workspace>/.config/gcloud/... |
Vertex AI access | ✓ |
| SSH key | $KEYFILE (specified via env var) |
/home/agent/.ssh/<keyname> |
Git SSH operations | ✓ |
| SSH config | Auto-generated in container | /home/agent/.ssh/config |
GitHub SSH settings | ✗ |
| Claude state | ~/.claude |
/home/agent/.claude |
Memory and todos | ✗ |
| Git config | Baked into image (.sandbox.gitconfig) |
/home/agent/.gitconfig |
Sandbox identity | ✗ |
| GitHub CLI config | ~/.config/gh |
/home/agent/.config/gh |
gh authentication | ✗ |
| GitHub token mapping | .sandbox.ghtoken (repo root) |
/home/agent/.ghtoken |
Dynamic GH_TOKEN lookup | ✓ |
Note on ADC path: Google Cloud credentials mount within your workspace. In a git repository, that's <repo-name>/.config/gcloud/...; outside a repository, it's workspace/.config/gcloud/.... The GOOGLE_APPLICATION_CREDENTIALS environment variable is automatically set to point to the correct location.
Note on SSH keys: The SSH key file specified by the KEYFILE environment variable is mounted read-only into the container. You must set KEYFILE to the path of your SSH key (e.g., KEYFILE=~/.ssh/id_ed25519).
Note on GitHub token mapping: If you want to use gh CLI API commands (e.g., gh pr create, gh issue list), create a .sandbox.ghtoken file in the repository root with SSH key to GitHub token mappings. Format: keyname|token (one per line). The entrypoint script automatically detects your mounted KEYFILE and looks up the token. This isolates gh credentials to specific SSH keys. If no mapping is found, GH_TOKEN is unset and gh commands requiring authentication will fail gracefully.
Note on SSH config: The container automatically generates an SSH configuration for GitHub at startup. Custom SSH hosts from your host's ~/.ssh/config are not included in the container — only GitHub SSH connectivity is configured. This works for both Docker and Podman.
Passphrase-protected keys: If your SSH key has a passphrase, use the SSH agent relay to access it securely in containers (see "Using SSH Keys with Passphrases" in Quick Start). The relay caches your passphrase on the host and bridges access to the container via TCP, so the passphrase never enters the container.
Passphrase-free keys: If you prefer not to use the relay, you can remove your SSH key's passphrase: ssh-keygen -p -f ~/.ssh/id_ed25519 -N "" -P "<passphrase>".
Note on git configuration: The container uses .sandbox.gitconfig which is baked into the Docker image. This isolates the sandbox from your host's git configuration, ensuring commits use the sandbox's configured identity (Jamie Mills / j6l288q4@1yz.xyz).
The script implements smart session management with graceful fallback:
Container Lifecycle:
- If a named container exists for your workspace, it's restarted and you reconnect
- If it doesn't exist, a new container is created and Claude Code starts fresh
- All state (memory, todos, etc.) is preserved across sessions
Resilience Strategy:
When starting a fresh container, the script attempts to continue the previous session using the --continue flag. If Claude Code detects state corruption or other issues, this may fail. In such cases, the script automatically falls back to restarting the container with a clean session, ensuring you never get stuck.
This design means interrupted or problematic sessions recover gracefully without manual intervention.
Set these before running ./claude-sandbox.sh to use Google Vertex AI:
export CLAUDE_CODE_USE_VERTEX=1 # Enable Vertex AI
export CLOUD_ML_REGION=us-central1 # Cloud region
export ANTHROPIC_VERTEX_PROJECT_ID=my-project-id # GCP projectGoogle Cloud Credentials: If you want to use Vertex AI, ensure ~/.config/gcloud/application_default_credentials.json exists on your host. See "Credential Handling" in the "How It Works" section for details.
Git Configuration: The container uses .sandbox.gitconfig which is included in the Docker image. To customise the git identity or settings for the sandbox, edit .sandbox.gitconfig in the repository root and rebuild the Docker image with ./build.sh.
GitHub Token: See "GitHub CLI (gh) Configuration" below for GitHub token setup.
The Docker image includes:
- Claude Code CLI — Anthropic's Claude Code tool
- Node.js 20.x — JavaScript runtime
- Python 3 — Python runtime with uv
- Git — Version control with GitHub CLI
- GitHub CLI (
gh) — GitHub command-line interface - SSH client — Secure shell access (pre-configured for GitHub)
- Vim — Text editor (default)
- curl & wget — HTTP clients
- tmux — Terminal multiplexing (pre-installed)
- socat — Socket relay utility (for SSH agent relay functionality)
The container aliases pip to uv pip for Python package management.
The container includes GitHub CLI (gh) with pre-configured SSH authentication and automatic token mapping based on your SSH keys.
Create a .sandbox.ghtoken file to map your SSH keys to GitHub tokens (one-time setup):
cat > .sandbox.ghtoken << 'EOF'
# Format: keyname|token
# The keyname is the basename of your SSH key file
# Example: if KEYFILE=~/.ssh/id_ed25519, use id_ed25519
id_ed25519|ghp_your_token_here
id_rsa|ghp_another_token_here
EOF
chmod 600 .sandbox.ghtokenReplace:
id_ed25519with your actual SSH key filename (basename only, e.g.id_ed25519orid_rsa)ghp_your_token_herewith your GitHub personal access token
The container automatically detects your mounted KEYFILE and looks up the corresponding token in .sandbox.ghtoken. If found, it sets GH_TOKEN for gh API commands.
Security note: .sandbox.ghtoken is listed in .gitignore to prevent accidental commits of tokens.
- When the container starts with
KEYFILE=~/.ssh/id_ed25519, the entrypoint script detects the key filename - It looks up
id_ed25519|...in the.sandbox.ghtokenmapping file - If found,
GH_TOKENis automatically set to the token - If not found,
GH_TOKENis unset (gh commands requiring authentication will fail gracefully) - Your
~/.config/ghdirectory is mounted read-write for configuration persistence - All gh commands use SSH authentication for git operations
# Inside container
gh repo list
gh pr create --title "My PR"
gh issue list
gh repo clone owner/repoAll gh commands work seamlessly using your SSH key for git operations and your mapped token for API access.
If gh commands fail:
# Check if GH_TOKEN is set
echo $GH_TOKEN
# Check gh auth status in container
gh auth status
# Check SSH authentication
ssh -T git@github.com
# Verify .sandbox.ghtoken exists and is mounted
cat ~/.ghtokenCommon issues:
- gh API commands fail: Ensure
.sandbox.ghtokenexists and contains the correct mapping for your KEYFILE - SSH fails: Ensure your SSH key (specified by KEYFILE) is correct and in
~/.ssh/ - Token not recognised: Verify the format is
keyname|tokenwith no spaces, and the keyname matches your SSH key filename (basename only)
The container includes built-in health checks that verify Claude Code is functioning correctly:
- Health check command: Runs
claude --versionevery 30 seconds - Startup grace period: 5 seconds (allows Claude Code to initialise)
- Timeout: 3 seconds per check
- Retry threshold: 3 consecutive failures before marking unhealthy
These health checks ensure your container is always in a reliable state. Docker automatically restarts unhealthy containers based on restart policies.
sandbox-claude/
├── README.md # User-facing documentation (this file)
├── CLAUDE.md # AI assistant guidelines and development notes
├── claude-sandbox.sh # Main entry point script (executable)
├── start-ssh-agent-relay.sh # SSH agent relay script for passphrase-protected keys
├── docker/
│ └── Dockerfile # Container image definition with all dependencies
├── .claude/
│ ├── settings.local.json # Claude Code permission allowlist
│ └── plans/ # Development plans and documentation
├── .config/ # (Optional) Host configuration directory
├── .git/ # Git repository metadata
└── .gitignore # Files to exclude from version control
Key Files:
claude-sandbox.sh— The main script you run; handles container lifecycle and mount strategystart-ssh-agent-relay.sh— Optional relay script to support passphrase-protected SSH keys in containersDockerfile— Defines the container image with Node.js, Python, Git, GitHub CLI, Claude Code, and socatCLAUDE.md— Contains guidelines for AI assistants working on this projectsettings.local.json— Explicitly allows certain commands (Docker, GitHub, gcloud) for Claude Code security
# Start Docker (macOS with Docker Desktop)
open /Applications/Docker.app
# Or, verify it's running
docker ps# Verify Google Cloud credentials exist
ls -la ~/.config/gcloud/application_default_credentials.json
# Verify SSH keys exist
ls -la ~/.sshFirst, ensure you've set the KEYFILE environment variable to point to your SSH key:
MODEL=haiku KEYFILE=~/.ssh/id_ed25519 ./claude-sandbox.shFor passphrase-protected keys:
Use the SSH agent relay to securely use password-protected keys in containers:
# On host: Start relay in tmux (caches passphrase automatically)
KEYFILE=~/.ssh/id_ed25519 tmux new-session -d -s ssh-relay './start-ssh-agent-relay.sh'
# Start container (relay provides SSH agent access)
MODEL=haiku KEYFILE=~/.ssh/id_ed25519 ./claude-sandbox.sh
# Inside container: SSH works without passphrase prompt
ssh -T git@github.comSee "Using SSH Keys with Passphrases (SSH Agent Relay)" in Quick Start for more details.
For passphrase-free keys:
If you prefer not to use the relay, remove your SSH key's passphrase:
ssh-keygen -p -f ~/.ssh/id_ed25519 -N "" -P "<your_current_passphrase>"Replace:
~/.ssh/id_ed25519with your key path (e.g.,~/.ssh/id_rsa)<your_current_passphrase>with your actual passphrase
Then verify inside the container:
# Inside container
ssh -T git@github.comThis should authenticate without prompting.
Check the Docker image built successfully:
docker images | grep claude_sandboxRebuild if needed:
docker build --no-cache -f docker/Dockerfile -t claude_sandbox .The container's working directory depends on where you run the script:
In a git repository:
# Running from repo root
cd /home/user/projects/my-app
./claude-sandbox.sh
# Container starts in /home/agent/my-app
# Running from subdirectory
cd /home/user/projects/my-app/src
./claude-sandbox.sh
# Container starts in /home/agent/my-app/src (relative path preserved)Outside a git repository:
cd /tmp/my-project
./claude-sandbox.sh
# Container always starts in /home/agent/workspaceTo verify the correct working directory inside the container:
pwdIf you're in the wrong directory, exit the container and re-run the script from the desired location.
Check for existing containers:
docker ps -aRemove stalled containers:
docker rm <container-id>Then restart the session script.
If the container exists but --continue fails (corrupted state), the script automatically falls back to restarting with a clean session, so you shouldn't get stuck.
Quick build (both Docker and Podman):
./build.shThis script:
- Stops all running containers using the
claude_sandboximage - Removes stopped containers to clean up old artifacts
- Rebuilds both Docker and Podman images with
--no-cacheto pick up all changes
Manual builds:
# Docker only
docker build -f docker/Dockerfile -t claude_sandbox .
# Podman only
podman build -f docker/Dockerfile -t claude_sandbox .Modify docker/Dockerfile and add apt-get install commands in the system dependencies section, then rebuild:
./build.shOr manually:
docker build -f docker/Dockerfile -t claude_sandbox .Edit docker/Dockerfile and modify the npm install line:
RUN npm install -g @anthropic-ai/claude-code@<version> && \
npm cache clean --forceVerify the image builds and tools are available:
docker build -f docker/Dockerfile -t claude_sandbox .
docker run --rm claude_sandbox claude --version
docker run --rm claude_sandbox node --version
docker run --rm claude_sandbox uv --version- The container runs as the unprivileged
agentuser - Credentials are mounted as read-only volumes
--group-add=rootis used for specific operations requiring elevated privileges- No secrets should be committed to the repository
.application_default_credentials.jsonis in.gitignoreto prevent accidental leaks
See LICENSE file for details.