Run Claude Code in --dangerously-skip-permissions mode inside an isolated Docker container. Your SSH keys, credentials, and host filesystem stay outside. Profiles let you maintain separate Claude contexts for different projects.
- Docker Desktop (Windows/macOS) or Docker Engine (Linux)
- A Claude Pro/Max subscription or an Anthropic API key
Claude will ask you on startup for a key and will give you a URL to get one if the browser doesn't launch.
1. Create a workspace directory and clone your repos into it
mkdir -p ~/sandboxes/myproject
cd ~/sandboxes/myproject
git clone <repo1>
git clone <repo2>2. Create a profile
cp .env.example .env.myproject
# Edit .env.myproject:
# SANDBOX_NAME=myproject
# WORKSPACE_PATH=/home/me/sandboxes/myproject (absolute path)
# ANTHROPIC_API_KEY=sk-ant-... # Option A: API key
# (or leave unset — browser OAuth prompt on first start, Option B)
# GIT_AUTHOR_NAME=Your Name
# [email protected]3. Start
# Linux/macOS
./claude/aid.sh start myproject
# Windows
.\claude\aid.ps1 start myprojectThe first start builds the Docker image (~5–15 minutes). Subsequent starts use the layer cache and are fast.
./claude/aid.sh start [profile] [subdir] # Build, start, launch Claude
./claude/aid.sh stop [profile] # Stop container
./claude/aid.sh shell [profile] # Open bash shell in container
./claude/aid.sh logs [profile] # Tail container logssubdir is optional — starts Claude in /workspace/<subdir> instead of the workspace root.
Omit profile to load .env (useful as a symlink to your current profile).
Claude sees all repos under /workspace/. Organise your workspace however you like — Claude Code loads per-repo CLAUDE.md and skills on demand as it works in each directory.
~/sandboxes/
├── myproject/ ← WORKSPACE_PATH for .env.myproject
│ ├── frontend/
│ └── backend/
└── data-pipeline/ ← WORKSPACE_PATH for .env.data-pipeline
├── etl-service/
└── shared-lib/
For HTTPS push/pull, add a personal access token to your profile as GIT_TOKEN=....
GitHub: github.com → Settings → Developer settings → Personal access tokens → Tokens (classic) → check repo scope
GitLab: gitlab.com → Edit profile → Access tokens → check read_repository + write_repository
Your SSH keys are never mounted into the container.
Each profile is a .env.<name> file. Two profiles with different SANDBOX_NAME values run simultaneously on the same machine without conflict.
# List available profiles
ls .env.*
# Run two profiles at once (open two terminals)
./claude/aid.sh start feature-a
./claude/aid.sh start data-pipeline
# Stop a specific profile
./claude/aid.sh stop feature-aClaude's history, MCP configuration, and settings are stored in claude/.claude-state/<SANDBOX_NAME>/. To give a new profile the same setup as an existing one:
cp -r claude/.claude-state/existing-profile/ claude/.claude-state/new-profile/The two profiles diverge independently from that point.
Point your profile at a local clone of a shared team config repository:
AGENT_CONFIG_PATH=/path/to/company-aid-configOn each container start, everything in that directory is copied into ~/.claude/ — instructions, skills, MCP configuration, etc. Employees clone the repo once and update it with git pull. If the repo contains managed-settings.json, it is also installed as org-level policy that overrides individual settings.
Enable a persistent Chromium instance that Claude and you share:
# In your .env.<profile>:
ENABLE_BROWSER=true
CHROMIUM_HOST_PORT=9222 # use a unique port per simultaneous profileTo drive the browser from Claude, install a Playwright MCP server inside the container (e.g. via AGENT_CONFIG_PATH pointing at a team config repo that includes an MCP config). You follow along in your host browser regardless:
- Open
chrome://inspectin your host Chrome - Click Configure... and add
localhost:9222 - The container's browser tab appears under Remote Target → click inspect
You can intervene at any point — click through a login, solve a CAPTCHA — and tell Claude to continue from where you left off.
ENABLE_DOCKER=trueClaude can run docker compose up, spin up databases, build images, etc.
Both aid.sh and aid.ps1 detect your platform and apply the appropriate security configuration automatically.
Linux (native) — rootless Docker-in-Docker. The daemon runs as the dev user with no host-root access. The container gets seccomp:unconfined and apparmor:unconfined to allow the user-namespace operations rootless Docker requires, but no privilege escalation. Inner containers cannot escalate to host root.
Windows, macOS, and WSL2 (Docker Desktop) — Docker Desktop's VM kernel blocks the user-namespace nesting that rootless Docker requires, so the container is started with --privileged. The Docker Desktop VM is the security boundary here; --privileged grants access to that VM, not your host OS. If that trade-off is not acceptable to you, leave ENABLE_DOCKER unset.
# Stop (preserves all state — history, Docker images, Chromium profile)
./claude/aid.sh stop myproject
# Wipe completely (removes container, then removes persistent state)
docker compose -f claude/docker-compose.yml -p myproject down
rm -rf claude/.claude-state/myproject # Linux/macOS
# Windows: Remove-Item -Recurse -Force claude\.claude-state\myprojectPersistent state lives in bind mounts (claude/.claude-state/<SANDBOX_NAME>/), not Docker-managed volumes, so down -v alone leaves it behind.
When Claude finishes a turn and waits for your input, your terminal bell rings and a visible separator appears:
━━━ Claude is waiting for your input ━━━
The bell flashes the taskbar/tab in Windows Terminal, iTerm2, macOS Terminal, and VS Code's integrated terminal.