feat(docker): add isolated Claude Code environment with proxy sidecar

Two-container setup: claude (UID 1000, internal-only network) and proxy
(Squid, UID 13). The internal Docker network uses internal: true so the
claude container has no direct internet route. All egress is tunnelled
through the Squid sidecar which enforces a domain allowlist. Both
containers drop all capabilities and set no-new-privileges. claude.sh
provides start/stop/run/update/logs/status/shell lifecycle management.
This commit is contained in:
docker-claude 2026-04-14 17:23:02 +02:00
commit e0e5e03e58
10 changed files with 554 additions and 0 deletions

59
docker-compose.yml Normal file
View file

@ -0,0 +1,59 @@
services:
# ─── Proxy sidecar ─────────────────────────────────────────────────────────
# Bridges the isolated internal network to the internet.
# Enforces an egress allowlist — see proxy/squid.conf.
proxy:
build:
context: .
dockerfile: Dockerfile.proxy
networks:
- claude-internal # reachable by the claude container
- proxy-external # has outbound internet access
restart: unless-stopped
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
read_only: true
tmpfs:
- /tmp
- /var/spool/squid
- /var/log/squid
# ─── Claude Code container ─────────────────────────────────────────────────
# No direct internet access. All egress routes through the proxy sidecar.
# Run via "docker compose run --rm claude" (managed by claude.sh).
claude:
build:
context: .
dockerfile: Dockerfile.claude
depends_on:
proxy:
condition: service_healthy
networks:
- claude-internal # only — no route to the internet
environment:
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
- HTTP_PROXY=http://proxy:3128
- HTTPS_PROXY=http://proxy:3128
- ALL_PROXY=http://proxy:3128
- NO_PROXY=localhost,127.0.0.1
security_opt:
- no-new-privileges:true
cap_drop:
- ALL
stdin_open: true
tty: true
# Workspace is injected by claude.sh via --volume flag at run time.
# Default: named Docker volume. Override: set WORKSPACE_DIR on the host.
networks:
# Internal-only: Docker adds no default gateway → no direct internet route
claude-internal:
driver: bridge
internal: true
# External: standard bridge with internet access (proxy only)
proxy-external:
driver: bridge