docker-claude/README.md

141 lines
5.1 KiB
Markdown
Raw Normal View History

2026-04-14 20:11:24 +02:00
# docker-claude
Runs [Claude Code](https://claude.ai/code) inside an isolated Docker environment with a proxy sidecar for controlled egress. Claude cannot access the host filesystem or network directly.
2026-04-14 20:11:24 +02:00
## Architecture
```
┌──────────────────────────────────────────────────────────┐
│ Host machine │
│ │
│ claude.sh (control script) │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Docker: claude-secure │ │
│ │ │ │
│ │ ┌─────────────┐ claude-internal │ │
│ │ │ claude │ (internal: true) │ │
│ │ │ (UID 1000) │──────────────► ┌──────────┐ │ │
│ │ └─────────────┘ │ proxy │ │ │
│ │ │ (UID 13) │ │ │
│ │ └────┬─────┘ │ │
│ │ proxy-external │ │
│ └──────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ internet (allowlisted) │
└──────────────────────────────────────────────────────────┘
2026-04-14 20:11:24 +02:00
```
- **`claude`** — Claude Code CLI (`node:20-alpine`), runs as the built-in `node` user (UID 1000), on `claude-internal` only
- **`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
2026-04-14 20:11:24 +02:00
## 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 credentials (see Authentication below)
2026-04-14 20:11:24 +02:00
cp .env.example .env
$EDITOR .env
2026-04-14 20:11:24 +02:00
# 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 <sandbox-name> --publish 54545:54545/tcp
```
Then run `./claude.sh start` and follow the prompt. Credentials are stored in
`~/.claude` on the host and reused on every subsequent run.
2026-04-14 20:11:24 +02:00
## Usage
```bash
# Start proxy, pull latest images, launch Claude Code in the current directory
cd ~/myproject
2026-04-14 20:11:24 +02:00
./claude.sh start
```
2026-04-14 20:11:24 +02:00
### Other commands
2026-04-14 20:11:24 +02:00
```bash
./claude.sh stop # Stop and remove all containers
./claude.sh update # Pull latest images from the registry
./claude.sh logs # Tail proxy logs
./claude.sh status # Show container status
./claude.sh shell # Debug bash shell in the Claude container
2026-04-14 20:11:24 +02:00
```
### Building locally
`build.sh` builds images from source using the local Dockerfiles:
```bash
./build.sh # build with layer cache
./build.sh --no-cache # force full rebuild
```
2026-04-14 20:11:24 +02:00
## Egress allowlist
Edit `proxy/squid.conf` and add domains to the `allowed_sites` ACL:
```
2026-04-14 20:11:24 +02:00
acl allowed_sites dstdomain api.anthropic.com
acl allowed_sites dstdomain statsig.anthropic.com
# acl allowed_sites dstdomain api.github.com
2026-04-14 20:11:24 +02:00
# acl allowed_sites dstdomain registry.npmjs.org
```
Rebuild after changes:
2026-04-14 20:11:24 +02:00
```bash
./claude.sh stop && ./claude.sh start
```
## Security controls
| Control | claude | proxy |
2026-04-14 20:11:24 +02:00
|---|---|---|
| Non-root user | UID 1000 (`node`, built into base image) | `squid` user |
2026-04-14 20:11:24 +02:00
| `no-new-privileges` | yes | yes |
| All capabilities dropped | yes | yes |
| Direct internet access | no (`internal` network only) | allowlisted only |
| Host filesystem | CWD mounted as `/workspace` | none |
2026-04-14 20:11:24 +02:00
| Docker socket | not mounted | not mounted |