# 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 │──┐ claude-internal │ │ │ │ │ (UID 1000) │ │ (internal: true) │ │ │ │ └─────────────┘ ├──────────────► ┌──────────┐ │ │ │ │ ┌─────────────┐ │ │ proxy │ │ │ │ │ │ webui │──┘ │ (UID 13) │ │ │ │ │ │ (UID 1000) │ └────┬─────┘ │ │ │ │ │ port 7681 │ proxy-external │ │ │ │ └─────────────┘ │ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ internet (allowlisted) │ └──────────────────────────────────────────────────────────┘ ``` - **`claude`** — Claude Code CLI (`node:20-alpine`), UID 1000, on `claude-internal` only - **`webui`** — Claude Code in a browser terminal via ttyd (`node:20-alpine`), UID 1000, on `claude-internal` only, port 7681 - **`proxy`** — Squid forward proxy (`alpine:3.21`), bridges `claude-internal` ↔ internet with egress allowlist - **`claude-internal`** — `internal: true`; no default gateway, containers cannot reach the internet directly - **`proxy-external`** — Standard bridge; proxy sidecar only ## 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 credentials (see Authentication below) cp .env.example .env $EDITOR .env # 3. Make the control script executable chmod +x claude.sh ``` ## Authentication Three options — pick one and set it in `.env`: ### Option 1 — API key ```bash ANTHROPIC_API_KEY=sk-ant-... ``` ### Option 2 — OAuth token (subscription, headless-friendly) Run this **on your host** (not inside the container) to generate a 1-year token: ```bash claude setup-token ``` Then set the printed token in `.env`: ```bash CLAUDE_CODE_OAUTH_TOKEN=... ``` ### Option 3 — Browser OAuth (interactive) Leave both keys unset. On first run, Claude Code will print a login URL. Port 54545 must be reachable from your browser for the OAuth callback: ```bash sbx ports --publish 54545:54545/tcp ``` Then run `./claude.sh run` and follow the prompt. Credentials are stored in the `claude-config` Docker volume and reused on every subsequent run. ## Usage ### CLI mode ```bash # Build images, start proxy, launch Claude Code interactively ./claude.sh start # Start proxy if needed, launch Claude Code (faster on subsequent runs) ./claude.sh run # Mount a host directory as the workspace WORKSPACE_DIR=$HOME/myproject ./claude.sh run ``` ### Web interface Serves Claude Code as a browser terminal via [ttyd](https://github.com/tsl0922/ttyd), protected by HTTP basic auth. ```bash # Add to .env first: # WEBUI_PASSWORD=your-strong-password # WEBUI_USER=claude # optional, defaults to "claude" ./claude.sh web # → Web interface running at http://0.0.0.0:7681 # To reach it from outside the sandbox host: sbx ports --publish 7681:7681/tcp # Stop web interface (keeps proxy running) ./claude.sh web-stop ``` ### Other commands ```bash ./claude.sh stop # Stop and remove all containers ./claude.sh update # Rebuild images without cache ./claude.sh logs # Tail proxy logs ./claude.sh logs webui # Tail web interface logs ./claude.sh status # Show container status ./claude.sh shell # Debug bash shell in the Claude container ``` ### Workspace | Mode | Default | Override | |---|---|---| | CLI (`run`/`start`) | Named Docker volume (isolated) | `WORKSPACE_DIR=/path ./claude.sh run` | | Web (`web`) | Named Docker volume (`claude-web-workspace`) | Edit `docker-compose.yml` volumes | ## Egress allowlist Edit `proxy/squid.conf` and add domains to the `allowed_sites` ACL: ``` acl allowed_sites dstdomain api.anthropic.com acl allowed_sites dstdomain statsig.anthropic.com # acl allowed_sites dstdomain api.github.com # acl allowed_sites dstdomain registry.npmjs.org ``` Rebuild after changes: ```bash ./claude.sh update ./claude.sh stop && ./claude.sh start ``` ## Security controls | Control | claude / webui | proxy | |---|---|---| | Non-root user | UID 1000 (`claude`) | `squid` user | | `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 | | Web auth | basic auth (ttyd `--credential`) | n/a |