claudebox: Claude Code in Docker, Now With Six Ways to Break Your Production Server

I use Claude Code for everything. Writing code, debugging shit, deploying infrastructure, managing repos, writing articles for this very blog, and even automating browser sessions on the fly. It’s become the backbone of how I work. But giving an AI agent full access to your system is fucking terrifying — not because Claude is malicious, but because it runs with --dangerously-skip-permissions and has the power to do whatever it wants. One bad command and your host is toast.
The obvious answer is: put it in a container. But actually doing that well is a whole other problem. I built claudebox to solve it. What started as a simple containerized Claude Code wrapper has grown into six different ways to run Claude — each one actually useful, none of them bullshit.

The Rename

This used to be called docker-claude-code, image psyb0t/claude-code, binary claude. It’s now claudebox, image psyb0t/claudebox, binary claudebox. SSH keys moved from ~/.ssh/claude-code to ~/.ssh/claudebox.
If you’re upgrading: uninstall the old binary, pull the new image, re-run the install script. Your ~/.claude config dir and session history survive the rename untouched.

Six Interfaces, One Container

claudebox isn’t just a wrapper anymore. It’s six different interfaces to Claude Code running inside Docker:

  • Interactive CLI — persistent container, session resumption, the original mode
  • Programmatic CLI — non-interactive, works from scripts and CI, its own dedicated container
  • HTTP API server — REST API with workspace management, file ops, sync and async runs
  • OpenAI-compatible endpoint — drop-in replacement at /openai/v1/chat/completions with streaming SSE
  • MCP server — five tools Claude can use from other agents via the Model Context Protocol
  • Telegram bot — per-chat workspaces, file sharing, shell commands from your phone

Install

curl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claudebox/master/install.sh | bash

This generates SSH keys at ~/.ssh/claudebox, pulls the image, and drops the claudebox binary at /usr/local/bin/claudebox. Then:

claudebox

First run prompts for authentication. After that it just works. The wrapper handles the entire container lifecycle — creates a new container if one doesn’t exist for the current directory, restarts and reattaches if one already does.

Image Variants

Full — psyb0t/claudebox:latest
Ubuntu base loaded with everything a developer actually needs: Go with the full toolchain (golangci-lint, gopls, delve), Python 3.12 via pyenv (flake8, black, mypy, pyright, vulture, pytest, poetry), Node.js LTS with the usual ecosystem, C/C++ toolchain, Docker CE with Compose, Terraform, kubectl, helm, GitHub CLI, database clients for SQLite/PostgreSQL/MySQL/Redis, and a pile of utilities (jq, ripgrep, fd-find, bat, shellcheck, shfmt, httpie). The container auto-generates a CLAUDE.md listing every available tool so Claude knows what it has to work with.
Minimal — psyb0t/claudebox:latest-minimal
Just the essentials: Ubuntu, git, curl, wget, jq, Node.js, Docker, Claude Code. Smaller image, faster pull. Claude has passwordless sudo so it installs what it needs on the fly. Use init hooks to pre-bake your setup so you’re not waiting on package installs every fresh container.

Interactive Mode

Run claudebox from any directory and you get a live session. Container persists between runs, session continues from where you left off. Each workspace gets its own container named after the directory path.
Utility commands:

claudebox --version        # show version
claudebox doctor           # health check
claudebox auth             # manage authentication
claudebox setup-token      # interactive OAuth token setup
claudebox stop             # stop the running container for this workspace
claudebox clear-session    # wipe session history, next run starts fresh
claudebox --update         # pull the latest image and reinstall

Session continuity. claudebox runs Claude with --continue, so it resumes the last conversation from the current directory. Kill the terminal, come back the next day, start it again — Claude picks up exactly where it left off. No session, no problem, it starts fresh.
UID/GID matching. The entrypoint detects the workspace owner and adjusts the container’s user to match. Files created inside the container have correct ownership on the host. No chown -R bullshit.

Programmatic Mode

Pass a prompt and claudebox runs non-interactively. Uses a dedicated _prog container per workspace — separate from the interactive one, no TTY required, works from scripts, cron, other tools:

# basic run
claudebox "explain this codebase"
# pick a model
claudebox "explain this codebase" --model sonnet
claudebox "audit this" --model opus
# output formats
claudebox "list all TODOs" --output-format json
claudebox "list all TODOs" --output-format json-verbose | jq .
claudebox "list all TODOs" --output-format stream-json | jq .
# reasoning effort
claudebox "debug this complex issue" --effort high
claudebox "quick question" --effort low
# custom system prompt
claudebox "review this" --system-prompt "You are a security auditor"
claudebox "review this" --append-system-prompt "Focus on SQL injection"
# structured output
claudebox "extract author and title" --output-format json \
  --json-schema '{"type":"object","properties":{"author":{"type":"string"},"title":{"type":"string"}}}'
# session control
claudebox "start over" --no-continue
claudebox "keep going" --resume abc123-def456

Model aliases: opus (Opus 4.6), sonnet (Sonnet 4.6), haiku (Haiku 4.5), opusplan (Opus for planning + Sonnet for execution), sonnet[1m] (Sonnet with 1M context window). Or pass a full model name to pin a specific version.
Output formats: text (default), json (single result object with cost and token breakdown), json-verbose (same as json but with a turns array showing every tool call, tool result, and assistant message — full visibility into what Claude did), stream-json (NDJSON, one event per line — system init, assistant responses, tool use, tool results, rate limit events, final result).

API Mode

Set CLAUDE_MODE_API=1 to run the container as an HTTP API server. Plug it into a docker-compose stack and other services can talk to Claude over HTTP:

services:
  claudebox:
    image: psyb0t/claudebox:latest
    ports:
      - "8080:8080"
    environment:
      - CLAUDE_MODE_API=1
      - CLAUDE_MODE_API_TOKEN=your-secret-token
      - CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx
    volumes:
      - ~/.claude:/home/claude/.claude
      - /your/projects:/workspaces
      - /var/run/docker.sock:/var/run/docker.sock

Endpoints:

  • POST /run — send a prompt, get a result. Fields: prompt, workspace, model, system_prompt, append_system_prompt, json_schema, effort, no_continue, resume. Returns 409 if the workspace is already processing.
  • POST /run with "async": true — returns a runId immediately. Poll GET /run/result?runId=X until it completes. Fire and forget.
  • POST /run/cancel — kill the running process for a workspace
  • GET /files/{path} — list directory or download file
  • PUT /files/{path} — upload a file (parent dirs created automatically)
  • DELETE /files/{path} — delete a file
  • GET /health — health check, no auth required
  • GET /status — show which workspaces are currently busy

All paths are relative to /workspaces. Auth via Bearer token in Authorization header — set CLAUDE_MODE_API_TOKEN to enable it. Busy workspace tracking returns 409 Conflict so you don’t accidentally queue up overlapping runs on the same workspace.

OpenAI-Compatible Endpoint

POST /openai/v1/chat/completions — a drop-in OpenAI adapter that routes requests to Claude Code inside the container. Works with anything that speaks the OpenAI API: LiteLLM, Open WebUI, custom clients, whatever.

curl http://localhost:8080/openai/v1/chat/completions \
  -H "Authorization: Bearer your-secret-token" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "sonnet",
    "messages": [{"role": "user", "content": "explain this codebase"}],
    "stream": true
  }'

Streaming works via Server-Sent Events. Multi-turn conversations work — pass the full message history and Claude maintains context. Multimodal works too — send base64 image content in the message and Claude can see it.
Custom headers to control behavior:

  • X-Claude-Workspace — which workspace to run in
  • X-Claude-Continue — whether to continue the previous session
  • X-Claude-Append-System-Prompt — append extra instructions to the system prompt

For LiteLLM, point it at http://your-host:8080/openai/v1 as a custom OpenAI provider and it works without any special configuration.

MCP Server

Enable the MCP server at /mcp/ to let other agents and tools call into your Claude container via the Model Context Protocol. Five tools exposed:

  • claude_run — run a prompt in a workspace, get the result back
  • list_files — list files in a workspace directory
  • read_file — read a file from a workspace
  • write_file — write a file to a workspace
  • delete_file — delete a file from a workspace

This means other Claude instances, custom agents, or any MCP-compatible client can use your claudebox instance as a tool — delegating work to a fresh Claude session with full file access.

Telegram Mode

Set CLAUDE_MODE_TELEGRAM=1 and you get a Telegram bot that talks to Claude. Each chat gets its own workspace and settings. Send text, files, photos, videos, voice messages. Run shell commands with /bash. Get files back with /fetch.
Configuration lives in a YAML file with per-chat model, effort, workspace, and system prompt:

# ~/.claude/telegram.yml
allowed_chats:
  - 123456789
  - -987654321
default:
  model: sonnet
  effort: high
  continue: true
chats:
  123456789:
    workspace: my-project
    model: opus
    effort: max
    system_prompt: "You are a senior engineer"
  -987654321:
    workspace: team-stuff
    model: sonnet
    allowed_users:
      - 123456789
services:
  claudebox-telegram:
    image: psyb0t/claudebox:latest
    environment:
      - CLAUDE_MODE_TELEGRAM=1
      - CLAUDE_TELEGRAM_BOT_TOKEN=123456:ABC-DEF
      - CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-xxx
    volumes:
      - ~/.claude:/home/claude/.claude
      - ~/telegram-workspaces:/workspaces
      - /var/run/docker.sock:/var/run/docker.sock

Bot commands: any text goes to Claude as a prompt. /bash <command> runs shell. /fetch <path> sends a file back to you. /cancel kills a running prompt. /status shows what’s currently processing. /config shows the chat’s settings. /reload hot-reloads the YAML without restarting the container.
Claude can push files back to you by putting [SEND_FILE: path] in its response. Images come through as photos, videos as videos, everything else as documents.

Customization

Always-Active Skills

Drop SKILL.md files into ~/.claude/.always-skills/ and they get auto-injected into every claudebox invocation — interactive, programmatic, API, Telegram, all of them. Persistent context that follows Claude into every session without touching individual project CLAUDE.md files.

Init Hooks

Scripts in ~/.claude/init.d/*.sh run once on first container create, as root, before dropping to the claude user. They don’t run again on subsequent docker start — only on fresh containers. Use this for extra package installs or one-time setup:

mkdir -p ~/.claude/init.d
cat > ~/.claude/init.d/setup.sh << 'EOF'
#!/bin/bash
apt-get update && apt-get install -y some-package
pip install some-library
EOF
chmod +x ~/.claude/init.d/setup.sh

Custom Scripts

Drop executables into ~/.claude/bin/ on the host and they're in PATH inside every container. Persists across all sessions, all workspaces.

Environment Variable Forwarding

Use the CLAUDE_ENV_ prefix to pass arbitrary env vars into the container — the prefix is stripped:

CLAUDE_ENV_GITHUB_TOKEN=xxx CLAUDE_ENV_MY_VAR=hello claudebox "do stuff"

Extra Volume Mounts

CLAUDE_MOUNT_ prefix to mount additional directories:

# Mount at same path on both sides
CLAUDE_MOUNT_DATA=/data claudebox "process the data"
# Explicit source:dest
CLAUDE_MOUNT_STUFF=/host/path:/container/path claudebox "do stuff"
# Read-only
CLAUDE_MOUNT_RO=/data:/data:ro claudebox "read the data"

The Workspace Model

claudebox creates two containers per workspace: claude-<path> for interactive sessions and claude-<path>_prog for programmatic runs. They don't share state and can run simultaneously. Interactive session doesn't block your scripts. Scripts don't interrupt your session.
The ~/.claude directory mounts into every container — configuration, API keys, always-skills, init hooks, custom scripts all shared across workspaces. SSH keys from ~/.ssh/claudebox mount in automatically. Isolation is at the workspace level, identity is shared.
Docker socket mounts through so Claude can build images, spin up compose stacks, manage containers from inside its own container. Since the workspace mounts at its real host path ($PWD:$PWD), volume mounts from inside Claude resolve correctly on the host. Claude writes a docker-compose.yml, runs it, the paths work.

Security Model

This runs with --dangerously-skip-permissions. Claude has passwordless sudo inside the container and can do whatever it wants.
The security boundary is the container. Claude can't touch your host filesystem beyond the mounted workspace and ~/.claude config. If it goes rogue, docker stop and docker rm and it's gone. Spin up a fresh one in seconds.
The Docker socket mount is the exception — it gives access to the host Docker daemon. Don't mount it if that concerns you. Everything else is contained.
SSH keys live in ~/.ssh/claudebox — a dedicated keypair generated during install. Your personal keys never enter the container. Configure which key is used via CLAUDE_SSH_DIR if needed.
API mode auth via Bearer token in the Authorization header. Set CLAUDE_MODE_API_TOKEN. If you don't set it, the API runs unauthenticated — fine for local use, bad idea exposed to a network.

The Bottom Line

I run every Claude session inside claudebox now. Six modes, zero host pollution. The isolation means I don't think twice about letting it install packages, rewrite configs, or run whatever commands it needs to get the job done. The session continuity means I close my terminal and come back hours later and pick up exactly where I left off. The API and OpenAI endpoint mean I can wire Claude into other services without writing glue code. The Telegram bot means I can kick off a task from my phone while I'm away from my desk.
Go grab it: github.com/psyb0t/docker-claudebox
Licensed under WTFPL — because the only thing more dangerous than an AI with root access is an AI with root access and a restrictive license.