120 lines
4.5 KiB
Markdown
120 lines
4.5 KiB
Markdown
# 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 <repo> 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 |
|