feat(proxy): containerized Claude Code client on isolated bridge

Adds proxy/ — a docker compose that routes Claude Code through
proxy.fhirworx.io from any host, parallel to the WSL/systemd bootstrap
in dev/scripts/setup-proxy-client.sh.

cf-proxy (cloudflared access tcp) and claude run on an inline
192.168.10.0/24 bridge. cf-proxy is pinned to .2 and claude reaches it
by hostname via extra_hosts so the TLS SNI to proxy.fhirworx.io
resolves correctly without sharing /etc/hosts with cloudflared.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
kert
2026-05-19 08:15:47 -04:00
parent 61d3000138
commit 6b02b9560e
3 changed files with 86 additions and 0 deletions

7
proxy/.env.example Normal file
View File

@@ -0,0 +1,7 @@
# Password for the squid 'claude' user (see infra/squid/passwd on the host).
PROXY_PASSWORD=
# Cloudflare Access service token for proxy.fhirworx.io.
# Mint a new pair under Zero Trust → Access → Service Auth.
CF_ACCESS_CLIENT_ID=
CF_ACCESS_CLIENT_SECRET=

13
proxy/Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
# Containerized Claude Code client — proxied via cf-proxy (sibling service).
# Mirrors what dev/scripts/setup-proxy-client.sh does on a WSL host, minus the
# systemd/hosts hackery (compose handles networking + extra_hosts).
FROM node:20-bookworm-slim
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
ca-certificates curl git jq openssh-client \
&& rm -rf /var/lib/apt/lists/*
RUN npm install -g @anthropic-ai/claude-code
ENTRYPOINT ["claude"]

66
proxy/compose.yml Normal file
View File

@@ -0,0 +1,66 @@
# Run Claude Code through proxy.fhirworx.io from inside containers.
#
# Two services on a self-contained 192.168.10.0/24 bridge:
# cf-proxy cloudflared access TCP shim — terminates the Cloudflare tunnel
# on 192.168.10.2:18443. Equivalent to the systemd --user unit
# that dev/scripts/setup-proxy-client.sh installs on a WSL host.
# claude Claude Code CLI. Reaches cloudflared via the bridge; extra_hosts
# pins proxy.fhirworx.io -> 192.168.10.2 so TLS SNI/cert match
# without poisoning cf-proxy's own DNS view.
#
# Usage:
# cp .env.example .env && $EDITOR .env # set PROXY_PASSWORD and
# CF_ACCESS_CLIENT_ID/SECRET
# docker compose run --rm claude # interactive session
# docker compose run --rm claude -p 'ping' # one-shot
#
# Notes:
# - claude-home is a named volume so login state persists across runs.
# - Bind mounts use identical host/container paths so Claude never sees a
# translated path: /home/care/acoharmony, /opt/s3/data/workspace,
# /opt/s3/data/notebooks.
services:
cf-proxy:
image: cloudflare/cloudflared:latest
container_name: cc-cf-proxy
command: access tcp --hostname proxy.fhirworx.io --url 0.0.0.0:18443
environment:
TUNNEL_SERVICE_TOKEN_ID: ${CF_ACCESS_CLIENT_ID}
TUNNEL_SERVICE_TOKEN_SECRET: ${CF_ACCESS_CLIENT_SECRET}
networks:
proxy_net:
ipv4_address: 192.168.10.2
restart: unless-stopped
claude:
image: git.fhirworx.io/kert/claude:latest
container_name: cc-claude
depends_on:
- cf-proxy
networks:
- proxy_net
extra_hosts:
- "proxy.fhirworx.io:192.168.10.2"
environment:
HTTPS_PROXY: "https://claude:${PROXY_PASSWORD}@proxy.fhirworx.io:18443"
HTTP_PROXY: "https://claude:${PROXY_PASSWORD}@proxy.fhirworx.io:18443"
NO_PROXY: "localhost,127.0.0.1"
working_dir: /home/care/acoharmony
volumes:
- claude-home:/root/.claude
- /home/care/acoharmony:/home/care/acoharmony
- /opt/s3/data/workspace:/opt/s3/data/workspace
- /opt/s3/data/notebooks:/opt/s3/data/notebooks
stdin_open: true
tty: true
networks:
proxy_net:
driver: bridge
ipam:
config:
- subnet: 192.168.10.0/24
volumes:
claude-home: