# docker-claude Runs [Claude Code](https://claude.ai/code) inside an isolated Docker environment with a proxy sidecar for controlled egress. Claude cannot reach the host filesystem or network directly. ## Architecture ``` ┌─────────────────────────────────────────────────────┐ │ Host machine │ │ │ │ claude.sh (control script) │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────┐ │ │ │ Docker: claude-secure │ │ │ │ │ │ │ │ ┌─────────────┐ claude-internal │ │ │ │ │ claude │◄─────(internal only)───► │ │ │ │ │ (UID 1000) │ │ │ │ │ │ └─────────────┘ ┌──────┴──────┐ │ │ │ │ │ proxy │ │ │ │ │ │ (UID 13) │ │ │ │ │ └──────┬──────┘ │ │ │ │ proxy-external │ │ │ └─────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ internet (allowlisted) │ └─────────────────────────────────────────────────────┘ ``` - **`claude` container** — Claude Code, runs as UID 1000, on `claude-internal` only (no internet route) - **`proxy` container** — Squid forward proxy, runs as UID 13, bridges `claude-internal` ↔ internet, enforces egress allowlist - **`claude-internal`** — Docker bridge with `internal: true`; Docker adds no default gateway, so containers on this network cannot reach the internet directly - **`proxy-external`** — Standard bridge; the proxy sidecar uses this for controlled outbound access ## Prerequisites - Docker Engine 24+ - Docker Compose v2 plugin (`docker compose version`) ## Setup ```bash # 1. Clone / copy this repo git clone docker-claude && cd docker-claude # 2. Configure your API key cp .env.example .env $EDITOR .env # set ANTHROPIC_API_KEY # 3. Make the control script executable chmod +x claude.sh ``` ## Usage ```bash # Build images, start proxy, launch Claude Code interactively ./claude.sh start # Same as start but skips image rebuild (faster on subsequent runs) ./claude.sh run # Stop and remove all containers (proxy + any running sessions) ./claude.sh stop # Rebuild images without cache (e.g. after Claude Code updates) ./claude.sh update # Tail proxy access logs ./claude.sh logs # Show container status ./claude.sh status # Open a debug bash shell inside the Claude container ./claude.sh shell ``` ### Working with host files By default, Claude's workspace is a named Docker volume (`claude-secure-workspace`) — fully isolated from the host. To mount a specific host directory: ```bash WORKSPACE_DIR=$HOME/myproject ./claude.sh run ``` The directory is mounted at `/workspace` inside the container. ## Egress allowlist Edit `proxy/squid.conf` and add domains to the `allowed_sites` ACL: ```squid acl allowed_sites dstdomain api.anthropic.com acl allowed_sites dstdomain statsig.anthropic.com # acl allowed_sites dstdomain api.github.com # uncomment if needed # acl allowed_sites dstdomain registry.npmjs.org ``` Rebuild the proxy after changes: ```bash docker compose -p claude-secure build proxy ./claude.sh stop && ./claude.sh start ``` ## Security controls | Control | Claude container | Proxy container | |---|---|---| | Non-root user | UID 1000 (`claude`) | UID 13 (`proxy`) | | `no-new-privileges` | yes | yes | | All capabilities dropped | yes | yes | | Direct internet access | no (`internal` network only) | allowlisted only | | Host filesystem | no mounts by default | none | | Docker socket | not mounted | not mounted |