Skip to content

jiangmuran/claude-in-box

Repository files navigation

claude-in-box — portable Claude Code dev environment with sessions, hooks, and a web API

English · 简体中文

Run Claude Code on a real server. Drive it from anywhere — browser, phone, IoT board, even an MCU — through one web port.

MIT licensed docker multi-arch amd64 / arm64


Overview

claude-in-box packages a full development environment together with Claude Code into a single Docker container and exposes it as a web service over one port.

The container is designed to run on a server (cloud VM, dedicated host, or a workstation that stays on). Inside, Claude Code runs in interactive REPL mode, which is a hard requirement: only interactive mode consumes the Anthropic subscription quota, while --print / headless invocations require an API key. The control plane then exposes that interactive session over the network.

Capabilities:

  • a sandboxed Linux box preloaded with a real dev environment — Node 22, Python 3 (with FastAPI + Uvicorn + Pydantic + httpx + rich + ipython), Go 1.25, Rust, plus nginx, redis-server, postgresql, and the Docker CLI/daemon — and Claude Code itself;
  • common tools out of the box: ripgrep, fd, bat, htop, tmux, vim, nano, openssh-client, less, file, tree, jq, curl, wget, build-essential, make;
  • bundled services do not auto-start; pass CIB_SERVICES=redis,postgres,nginx (any subset of redis, postgres, nginx, docker) and the entrypoint brings them up before the control plane;
  • one or many virtual-TTY sessions running inside it, each a live Claude Code conversation in bypass-permission mode (the container is the sandbox, so per-tool prompts are unnecessary friction);
  • a Web UI that surfaces three concurrent views on the same session — raw virtual terminal, web-native structured Claude driver, and an API inspector for developers;
  • structured event streaming: text deltas, tool calls, todo updates, token usage, status changes, stop reasons, model metadata — all available as JSON frames over WebSocket or SSE, never screen-scraped from the TTY;
  • session lifecycle controls: create, attach, resume, kill, switch models on the fly;
  • two ways to bill Claude per session: an Anthropic subscription (signed in via the in-container OAuth flow driven by the Web UI), or an API key;
  • a Web API in multiple wrappings off one port: our native frame schema (REST + WS + SSE + AES envelope), Anthropic-compatible POST /v1/messages and OpenAI-compatible POST /openai/v1/chat/completions adapters with full incremental SSE streaming so existing SDKs can target the box as a drop-in, plus an MCU-friendly slim chat (/sse/sessions/{id}/chat or /aes/sessions/{id}/chat) and a one-shot /api/sessions/{id}/send send-and-wait endpoint;
  • a transparent SOCKS5 layer so every outbound packet from inside the box can be rerouted through one upstream proxy without per-tool config;
  • programmable hooks on every lifecycle event;
  • a single multi-arch image (linux/amd64, linux/arm64) that boots equally cleanly on x86 servers and Ampere-class arm64 hosts.

The intent is to decouple Claude Code from a single workstation: deploy it once on a server, then access it from any device using whichever transport and API shape fits the client.

Typical workflow

1.  Pick an environment image: prebuilt :latest or your own custom build on
    top of it.
2.  Forward one port (8080 by default).
3.  docker run — the container boots the control plane on :8080, multiplexing
    Web UI + REST + WS + SSE + AES envelope on the same port.
4.  Open the web panel. Authenticate with the master API key minted at boot.
5.  Sign in once via the Web UI to choose how Claude is billed:
    an Anthropic subscription (the unified auth modal drives an
    OAuth flow inside the container) or an API key. The setting
    persists; new sessions inherit it.
6.  Dashboard shows: live sessions, token consumption, wall-clock work time,
    current model, hook activity.
7.  Create a new session. The panel gives you three concurrent views on it:
       (a) raw virtual terminal — xterm.js bound to Claude Code's PTY, the
           native CC TUI as you'd see in iTerm;
       (b) web-native Claude driver — chat-style transcript + todo sidebar
           + tool-call timeline + token meter + model picker;
       (c) API inspector — every frame and every API request/response,
           devtools-style.
    You can switch between them or open them side-by-side.
8.  Talk to Claude. Switch models mid-flight. Watch todos, tool calls, and
    status update live in the structured panes — they are rendered from a
    typed event stream, not by screen-scraping the terminal.
9.  From a phone, tablet, embedded MCU, or another agent, hit the same
    sessions over the transport and API shape that suits the client —
    REST/WS for browsers, AES envelope or SSE for an ESP32, Anthropic- or
    OpenAI-compatible HTTP for off-the-shelf SDKs (shipped — see
    `docs/API.md`).

The remainder of this document describes each layer in more depth.

Capabilities

Sessions and Claude Code

Capability Notes
Multi-session PTY-backed; spawn, attach, detach, kill, list. Multiple clients can attach to the same session simultaneously.
Interactive REPL only Claude Code is run in its full interactive mode, never with --print. This is mandatory for subscription-quota billing and is what powers the structured event stream via hooks.
Bypass-permission mode Default. Claude Code runs with --dangerously-skip-permissions because the container is the security boundary, not the per-tool prompt. Can be turned off per session; hook PermissionRequest events still fire and can re-authoritate.
Resume Sessions are CC's own — transcript lives at ~/.claude/projects/<hash>/<session>.jsonl. POST /api/sessions { resume: <session_id> } re-spawns with --resume.
Model switching POST /api/sessions/:id/model { model } sends /model <name> into the PTY mid-session and emits a meta frame.
Input simulation POST /api/sessions/:id/input writes raw bytes (or text frames) into the session's stdin. Same primitive backs both human typing and automation.
Detached / headless Sessions survive client disconnects. Reconnect with ?from=<seq> to replay missed frames.

Claude authentication (per session)

Mode When to use How
Anthropic subscription (default for personal use) You pay for Claude Pro / Max and want sessions billed against that subscription. Sign in from the Web UI's auth modal — it drives a PTY-backed claude /login flow inside the container and persists the credentials in the mounted ~/.claude/ volume.
API key Programmatic, CI, per-token billing, or sharing one box across users that bring their own keys. Set ANTHROPIC_API_KEY on the container, or configure a provider in the Web UI for per-session selection.
Long-lived OAuth token (legacy) Useful for headless CI before the interactive flow shipped. Note: claude setup-token-issued tokens move to a separate Agent SDK billing quota after 2026-06-15 and no longer consume the interactive subscription. Prefer the interactive flow above. Pass as CLAUDE_CODE_OAUTH_TOKEN.
Third-party Anthropic-compatible host You want to point at jmrai.net or your own proxy with a different api_host. Built-in jmrai.net preset in the auth modal — paste your key, save, done. Custom host/key/label also supported.
Mutual exclusion Subscription and API-key paths cannot both be active at once. Configuring an API provider wipes claude.ai credentials; logging into subscription deletes all configured providers. Enforced server-side.

Subscription billing only works because CC stays in interactive REPL mode inside the container — see the row above.

Structured event stream

The streaming bridge does not just relay terminal bytes. It parses Claude Code's lifecycle into typed frames that any client can render without screen-scraping. Every frame carries session, seq, ts.

Frame type Emitted when Payload fields
text.delta Assistant text streams text
thinking Extended-thinking block text (optional, gated by config)
tool.use.start Tool invocation begins tool, input
tool.use.result Tool returns tool, output, error?, duration_ms
todo.update TodoWrite / TodoUpdate fires items: [{ id, subject, status, activeForm? }]
ask.question Model asks the user to pick prompt, options[], multiSelect
usage End of turn input, output, cache_read, cache_write
status Session state changes state in idle / working / waiting_for_input / stopped, elapsed_ms
stop Turn or session ends reason
meta Model or config changes model, workdir, …
hook A user hook fired name, event, payload, result?
pty.raw Optional opaque PTY bytes data (off by default, on for terminal-style clients)

Clients pick which frames they care about: a phone dashboard probably wants todo.update, usage, status, stop; a terminal emulator wants pty.raw; a watchdog wants only status and stop.

Hooks

Hooks are first-class. The control plane installs its own http-type hooks at session start (HMAC-signed, pointed at an internal route) so it can capture every lifecycle event authoritatively. User-supplied hooks compose on top, merged from image-level (/etc/claude-in-box/hooks.json), user-level (~/.claude/hooks.json), and per-session declarations. Hooks can rewrite, block, inject context, or annotate; results land on the frame bus as hook frames.

Web API: one port, many wrappings

The container exposes exactly one TCP port. Everything is multiplexed onto it through HTTP routing. Each capability is wrapped in multiple shapes so very different clients can use the same backend with the format they prefer.

Wrapping Path prefix Best for Crypto Auth
Native frame REST + WS /api/*, /ws/* Browser, phone, server, our Web UI TLS via nginx Bearer token (master or device-scoped)
Native frame SSE /sse/* Cheap one-way clients, log tailers TLS via nginx Bearer
HTTP + AES envelope /aes/* Bare-metal MCU (ESP32, STM32) without a TLS stack AES-256-GCM per-device key, v2 record-stream API key + per-request nonce
Anthropic-compatible API /v1/messages (+ stream=true SSE) @anthropic-ai/sdk etc. — point base_url at the box and get subscription-backed Claude TLS via nginx Bearer / API key
OpenAI-compatible API /openai/v1/chat/completions openai / openai-node — same idea, OpenAI wire shape TLS via nginx Bearer / API key
Slim chat (embedded) /api/sessions/{id}/chat, /sse/sessions/{id}/chat, /aes/sessions/{id}/chat MCU clients with a few hundred KB of RAM TLS / AES envelope Bearer / AES key
Send-and-wait POST /api/sessions/{id}/send One HTTP round-trip per turn for non-streaming clients TLS via nginx Bearer
Port mapping /api/ports/* Surface an in-container service on a host port via socat (needs CIB_PORT_RANGE on docker run) TLS via nginx Bearer
MQTT bridge IoT bus integrations (not yet built) TLS or pre-shared Per topic
Raw TCP framed Absolute minimum footprint (not yet built) AES-GCM API key

The Anthropic- and OpenAI-compatible adapters are format adapters over the same session bus, not parallel runtimes. They let any tool that already speaks those APIs route through the box and pick up subscription-backed Claude.

For HTTPS deployments we ship an nginx template that terminates TLS, proxies the REST surface, upgrades WebSocket connections, holds SSE open, and forwards client IPs.

For the embedded HTTP transport we ship a small protocol spec, docs/AES-TRANSPORT.md, so device firmware authors can implement it in a few hundred lines with any AES-GCM library.

Auth on the control plane

  • A master API key is minted at container boot via CIB_AUTH_TOKEN. The control plane refuses to start without one (override only for local dev).
  • Device tokens can be issued via the API. Each has a label, scope set, and optional TTL. Revocable independently.
  • WebSocket auth travels in the Sec-WebSocket-Protocol subprotocol header to keep tokens out of URL logs.
  • OIDC is planned via a fronting reverse proxy (oauth2-proxy / authelia). The control plane honors X-Forwarded-User.

Network: transparent SOCKS5

Set CIB_PROXY_URL=socks5://user:pass@host:port once at boot and every outbound TCP (and UDP through tun2socks where supported) from inside the box is redirected through that proxy. Claude API calls, npm install, pip install, apt, git push — all of it, with no per-app config. Implemented via redsocks plus nftables.

Embedded clients (not the server)

The server side is intentionally not sized for embedded hosts — running CC in interactive mode against subscription quota wants a real machine. What is embedded-friendly is the client side:

  • The AES envelope HTTP transport is designed so an ESP32 / STM32 / RP2040 with only an HTTP client and an AES-GCM implementation can be a first-class participant: send input to a session, poll for structured frames, react to todos / stop events.
  • A reference C client lives at clients/c/ (mbedtls + libcurl, ~300 LOC), with a sibling ESP-IDF example.
  • A reference Python client lives at clients/python/ (stdlib + cryptography, ~250 LOC, with tests).
  • A Rust reference client is the next on the list.

See docs/ARCHITECTURE.md for the system in more depth.

Architecture (high level)

                                                       ┌────────────────────────────────────────────┐
                                                       │            claude-in-box container          │
                                                       │            (real server only)               │
   Browser / phone / iPad   ── /api  /ws  /sse  ───▶   │  ┌────────────┐    ┌──────────────────┐    │
   Server / CI / agent      ── /api  /ws  /sse  ───▶   │  │  control   │◀──▶│ session manager  │──┐ │
   Existing Claude SDK      ── /v1/messages*    ───▶   │  │   plane    │    │  (PTY-backed,    │  │ │
   Existing OpenAI SDK      ── /openai/v1/chat* ───▶   │  │  (single   │    │   interactive,   │  │ │
   ESP32 / STM32 / MCU      ── /aes/...          ───▶  │  │   :8080,   │    │   bypass-perm,   │  │ │
   Watchdog / dashboard     ── /sse              ───▶  │  │   multi-   │    │   resumable)     │  │ │
                                                       │  │   wrapped) │    └──────────────────┘  │ │
                                                       │  │            │            ▲             ▼ │
                                                       │  │  + auth    │            │     ┌──────────┐
                                                       │  └────────────┘            └────▶│  hooks   │
                                                       │        ▲                          │  runtime │
                                                       │        │  structured frames       └──────────┘
                                                       │        │  text.delta / tool.use         │   │
                                                       │        │  todo.update / usage           │   │
                                                       │        │  status / stop / meta          │   │
                                                       │        │                                │   │
                                                       │        │              ┌─────────────────┴──┐│
                                                       │        └──────────────┤ session files +    ││
                                                       │                       │ transcript.jsonl   ││
                                                       │                       └────────────────────┘│
                                                       │                                            │
                                                       │   Claude Code  ◀── pty ──  session N       │
                                                       │   Claude Code  ◀── pty ──  session 2       │
                                                       │   Claude Code  ◀── pty ──  session 1       │
                                                       │                ▲                            │
                                                       │                │  Anthropic subscription    │
                                                       │                │  (OAuth long token)        │
                                                       │                │       or API key           │
                                                       │     ┌──────────┴────────┐                  │
                                                       │     │ transparent socks5│  ◀── optional    │
                                                       │     │ (redsocks + nft)  │     PROXY_URL    │
                                                       │     └───────────────────┘                  │
                                                       └────────────────────────────────────────────┘

Quick start

docker run -d --name claude-box \
  -p 8080:8080 \
  --cap-add NET_ADMIN \
  -e CIB_AUTH_TOKEN=$(openssl rand -hex 32) \
  `# optional headless creds: ANTHROPIC_API_KEY or CLAUDE_CODE_OAUTH_TOKEN (legacy)` \
  -e CIB_PROXY_URL=socks5://user:pass@proxy.example:1080 \
  -e CIB_SERVICES=redis,postgres                    `# auto-start bundled services` \
  -e CIB_PORT_RANGE=9000-9019 -p 9000-9019:9000-9019 `# optional: expose in-container services on host ports` \
  -v $(pwd)/workspace:/workspace \
  -v $(pwd)/sessions:/var/lib/claude-in-box/sessions \
  -v $(pwd)/claude-home:/home/coder/.claude \
  -v /var/run/docker.sock:/var/run/docker.sock      `# optional: talk to host Docker` \
  ghcr.io/jiangmuran/claude-in-box:latest

open http://localhost:8080

The master token (CIB_AUTH_TOKEN) is what you paste into the Web UI on first visit; mint device-scoped tokens from there.

API-only mode (no Web UI on /, only /api/* /ws/* /sse/* /aes/* /v1/* /openai/v1/*) — same image, just a runtime flag:

docker run -d --restart unless-stopped \
  -p 8080:8080 \
  -e CIB_MODE=headless \
  -e CIB_AUTH_TOKEN=... \
  ghcr.io/jiangmuran/claude-in-box:latest

Behind HTTPS via nginx: see deploy/nginx.conf.template.

Implementing the AES envelope on a microcontroller: see docs/AES-TRANSPORT.md.

Contributing

Issues and pull requests are welcome. For client-device integrations with specific constraints, opening an issue first makes alignment easier. Small, focused changes are preferred over broad refactors.

License

MIT

About

Portable Claude Code dev environment in a Docker container — multi-session, hook-driven, web-managed, with transparent SOCKS5. Runs on a Raspberry Pi.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors