Skip to content

psyb0t/docker-claude-code

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

62 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🧠 docker-claude-code

claude but dockerized, goth-approved, and dangerously executable. This container gives you the Claude Code in a fully isolated ritual circle – no cursed system installs required.

💀 Why?

Because installing things natively is for suckers. This image is for devs who live dangerously, commit anonymously, and like their AI tools in containers.

🎞️ What's Inside?

  • Ubuntu 22.04 (stable and unfeeling)
  • Go 1.25.5 with full toolchain (golangci-lint, gopls, delve, staticcheck, gofumpt, gotests, impl, gomodifytags)
  • Latest Node.js with comprehensive dev tools (eslint, prettier, typescript, ts-node, yarn, pnpm, nodemon, pm2, framework CLIs, newman, http-server, serve, lighthouse, storybook)
  • Python 3.12.11 via pyenv with linters, formatters, testing (flake8, black, isort, autoflake, pyright, mypy, vulture, pytest, poetry, pipenv)
  • Python libraries pre-installed (requests, beautifulsoup4, lxml, pyyaml, toml)
  • Docker CE with Docker Compose (full containerization chaos)
  • DevOps tools (terraform, kubectl, helm, gh CLI)
  • System utilities (jq, tree, ripgrep, bat, exa, fd-find, silversearcher-ag, htop, tmux)
  • Shell tools (shellcheck, shfmt)
  • C/C++ tools (gcc, g++, make, cmake, clang-format, valgrind, gdb, strace, ltrace)
  • Database clients (sqlite3, postgresql-client, mysql-client, redis-tools)
  • Editors (vim, nano)
  • Archive tools (zip, unzip, tar)
  • Networking tools (net-tools, iputils-ping, dnsutils)
  • git + curl + wget + httpie + Claude Code
  • Auto-Git config based on env vars
  • Auto-generated CLAUDE.md in workspace (lists all available tools for Claude's awareness)
  • Startup script that configures git, updates claude, and runs with --dangerously-skip-permissions --continue (falls back to fresh session if no conversation to continue)
  • Auto-updates claude on interactive startup (skip with --no-update), background auto-updater disabled
  • Workspace trust dialog is automatically pre-accepted (no annoying prompts)
  • Programmatic mode support — just pass a prompt and optional --output-format (-p is added automatically)
  • Custom scripts via ~/.claude/bin — drop executables there and they're in PATH inside the container
  • Init hooks via ~/.claude/init.d/*.sh — run once on first container create (not on subsequent starts)
  • Debug logging (DEBUG=true) with timestamps in both wrapper and entrypoint

📋 Requirements

  • Docker installed and running

⚙️ Quick Start

🚀 Quick Install

There's an install script that sets everything up automatically:

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

To install as a different binary name (e.g. to avoid collision with a native claude install):

# as argument
curl -fsSL .../install.sh | bash -s -- dclaude

# or via env var
CLAUDE_BIN_NAME=dclaude curl -fsSL .../install.sh | bash

Or if you prefer manual control:

Create settings dir

mkdir -p ~/.claude

🥪 Generate SSH Keys

If you don't have an SSH key pair yet, conjure one with:

mkdir -p "$HOME/.ssh/claude-code"
ssh-keygen -t ed25519 -C "claude@claude.ai" -f "$HOME/.ssh/claude-code/id_ed25519" -N ""

Then add the public key ($HOME/.ssh/claude-code/id_ed25519.pub) to your GitHub account or wherever you push code.

🔐 ENV Vars

Variable What it does Default
CLAUDE_GIT_NAME Git commit name inside the container (none)
CLAUDE_GIT_EMAIL Git commit email inside the container (none)
ANTHROPIC_API_KEY API key for authentication (none)
CLAUDE_CODE_OAUTH_TOKEN OAuth token for authentication (none)
CLAUDE_DATA_DIR Custom .claude data directory (config, sessions, auth, plugins) ~/.claude
CLAUDE_SSH_DIR Custom SSH key directory ~/.ssh/claude-code
CLAUDE_INSTALL_DIR Custom install path for the wrapper script /usr/local/bin
CLAUDE_BIN_NAME Custom binary name (alternative to passing as argument) claude
CLAUDE_ENV_* Forward custom env vars to the container (prefix is stripped) (none)
CLAUDE_MOUNT_* Mount extra volumes (path alone = same path in container, or src:dest) (none)
DEBUG Enable debug logging with timestamps in wrapper and entrypoint (none)

To set these, export them on your host machine (e.g. in your ~/.bashrc or ~/.zshrc):

export CLAUDE_GIT_NAME="Your Name"
export CLAUDE_GIT_EMAIL="your@email.com"

If not set, git inside the container won't have a default identity configured.

Authentication

Either log in interactively or set up a long-lived OAuth token:

# generate an OAuth token (interactive, one-time setup)
claude setup-token

# then use it for programmatic runs
CLAUDE_CODE_OAUTH_TOKEN=xxx claude "do stuff"

# or use an API key
ANTHROPIC_API_KEY=sk-ant-xxx claude "do stuff"

Custom env vars

Use the CLAUDE_ENV_ prefix to forward arbitrary env vars into the container. The prefix is stripped:

# GITHUB_TOKEN=xxx and MY_VAR=hello will be set inside the container
CLAUDE_ENV_GITHUB_TOKEN=xxx CLAUDE_ENV_MY_VAR=hello claude "do stuff"

Extra volume mounts

Use the CLAUDE_MOUNT_ prefix to mount additional directories into the container:

# mount at the same path inside the container (just specify the host path)
CLAUDE_MOUNT_DATA=/data claude "process the data"

# mount multiple directories
CLAUDE_MOUNT_1=/opt/configs CLAUDE_MOUNT_2=/var/logs claude "check logs"

# explicit source:dest mapping
CLAUDE_MOUNT_STUFF=/host/path:/container/path claude "do stuff"

# read-only mount
CLAUDE_MOUNT_RO=/data:/data:ro claude "read the data"

If the value contains :, it's used as-is (docker -v syntax). Otherwise, the path is mounted at the same location inside the container.

Custom paths

# custom .claude data directory
CLAUDE_DATA_DIR=/path/to/.claude claude "do stuff"

# custom SSH key directory
CLAUDE_SSH_DIR=/path/to/.ssh claude "do stuff"

# install to a different directory
CLAUDE_INSTALL_DIR=/usr/bin curl -fsSL .../install.sh | bash

🧙 Usage

Interactive mode

claude

Starts an interactive session. The container is named by directory path and persists between runs — stop/restart instead of attach, with --continue to resume the last conversation. Claude auto-updates on each interactive start. To skip:

claude --no-update

Programmatic runs never auto-update.

Programmatic mode

Just pass a prompt — -p is added automatically:

# one-shot prompt with JSON output
claude "explain this codebase" --output-format json

# use a specific model
claude "explain this codebase" --model sonnet
claude "explain this codebase" --model claude-sonnet-4-6

# streaming output piped to jq
claude "list all TODOs" --output-format stream-json | jq .

# plain text output (default)
claude "what does this repo do"

Uses its own _prog container (no TTY — works from scripts, cron, other tools). --continue is passed automatically so programmatic runs share session context via the mounted .claude data dir.

Model selection

Use --model to pick which Claude model to use:

Alias Model Best for
opus Claude Opus 4.6 Complex reasoning, architecture, hard debugging
sonnet Claude Sonnet 4.6 Daily coding, balanced speed/intelligence
haiku Claude Haiku 4.5 Quick lookups, simple tasks, high volume
opusplan Opus (planning) + Sonnet (execution) Best of both worlds
sonnet[1m] Sonnet with 1M context Long sessions, huge codebases

You can also use full model names to pin specific versions:

Full model name Notes
claude-opus-4-6 Current Opus
claude-sonnet-4-6 Current Sonnet
claude-haiku-4-5-20251001 Current Haiku
claude-opus-4-5-20251101 Legacy
claude-sonnet-4-5-20250929 Legacy
claude-opus-4-1-20250805 Legacy
claude-opus-4-20250514 Legacy (alias: claude-opus-4-0)
claude-sonnet-4-20250514 Legacy (alias: claude-sonnet-4-0)
claude-3-haiku-20240307 Deprecated, retiring April 2026
claude "do stuff" --model opus                        # latest opus
claude "do stuff" --model haiku                       # fast and cheap
claude "do stuff" --model claude-sonnet-4-5-20250929  # pin to specific version

If not specified, the model defaults based on your account type (Max/Team Premium → Opus, Pro/Team Standard → Sonnet).

Output formats

text (default) — plain text response.

json — single JSON object with the result:

{
  "type": "result",
  "subtype": "success",
  "is_error": false,
  "result": "the response text",
  "num_turns": 1,
  "duration_ms": 3100,
  "duration_api_ms": 3069,
  "total_cost_usd": 0.156,
  "session_id": "...",
  "usage": { "input_tokens": 3, "output_tokens": 4, "..." : "..." },
  "modelUsage": { "..." : "..." }
}

stream-json — newline-delimited JSON (NDJSON), one event per line. Each event has a type field. Here's what a multi-step run looks like (e.g. claude "install cowsay, run it, fetch a URL" --output-format stream-json):

system — first event, session init with tools, model, version, permissions:

{"type":"system","subtype":"init","cwd":"/your/project","session_id":"...","tools":["Bash","Read","Write","Glob","Grep","..."],"model":"claude-opus-4-6","permissionMode":"bypassPermissions","claude_code_version":"2.1.62","agents":["general-purpose","Explore","Plan","..."],"skills":["keybindings-help","debug"],"plugins":[...],"fast_mode_state":"off"}

assistant — Claude's responses. Content is an array of text and/or tool_use blocks:

{"type":"assistant","message":{"model":"claude-opus-4-6","role":"assistant","content":[{"type":"text","text":"I'll install cowsay first."}],"usage":{"input_tokens":3,"output_tokens":2,"cache_read_input_tokens":22077,"...":"..."}},"session_id":"..."}

When Claude calls a tool, content contains a tool_use block:

{"type":"assistant","message":{"model":"claude-opus-4-6","role":"assistant","content":[{"type":"tool_use","id":"toolu_abc123","name":"Bash","input":{"command":"sudo apt-get install -y cowsay","description":"Install cowsay"}}],"usage":{"input_tokens":1,"output_tokens":26,"...":"..."}},"session_id":"..."}

user — tool execution results (stdout, stderr, error status):

{"type":"user","message":{"role":"user","content":[{"tool_use_id":"toolu_abc123","type":"tool_result","content":"Setting up cowsay (3.03+dfsg2-8) ...","is_error":false}]},"session_id":"...","tool_use_result":{"stdout":"Setting up cowsay (3.03+dfsg2-8) ...","stderr":"","interrupted":false}}

rate_limit_event — rate limit status check between turns:

{"type":"rate_limit_event","rate_limit_info":{"status":"allowed","resetsAt":1772204400,"rateLimitType":"five_hour","overageStatus":"allowed","isUsingOverage":false},"session_id":"..."}

result — final event with summary, cost, usage breakdown per model:

{"type":"result","subtype":"success","is_error":false,"num_turns":10,"duration_ms":60360,"duration_api_ms":46285,"total_cost_usd":0.203,"result":"Here's what I did:\n1. Installed cowsay...\n2. ...","session_id":"...","usage":{"input_tokens":12,"output_tokens":1669,"cache_read_input_tokens":255610,"cache_creation_input_tokens":5037},"modelUsage":{"claude-opus-4-6":{"inputTokens":12,"outputTokens":1669,"cacheReadInputTokens":255610,"costUSD":0.201},"claude-haiku-4-5-20251001":{"inputTokens":1656,"outputTokens":128,"costUSD":0.002}}}

A typical multi-step run produces: system → (assistantuser)× repeated per tool call → rate_limit_event between turns → final assistant text → result.

🔧 Customization

Custom scripts (~/.claude/bin)

Drop executables into ~/.claude/bin/ on the host and they're in PATH inside every container session:

mkdir -p ~/.claude/bin
echo '#!/bin/bash
echo "hello from custom script"' > ~/.claude/bin/my-tool
chmod +x ~/.claude/bin/my-tool

# now available inside the container
claude  # my-tool is in PATH

Init hooks (~/.claude/init.d)

Scripts in ~/.claude/init.d/*.sh run once on first container create (as root, before dropping to claude user). They don't run again on subsequent docker start — only on fresh docker run after a container is removed.

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

Useful for installing extra packages, configuring services, or any one-time setup that should survive container restarts but re-run on fresh containers.

🦴 Gotchas

  • This tool uses --dangerously-skip-permissions. Because Claude likes to live fast and break sandboxes.
  • SSH keys are mounted to allow commit/push shenanigans. Keep 'em safe, goblin.
  • The host directory is mounted at its exact path inside the container (e.g. /home/you/project stays /home/you/project). This means docker volume mounts from inside Claude will use correct host paths.
  • The container user's UID/GID is automatically matched to the host directory owner, so file permissions just work.
  • Docker socket is mounted so Claude can spawn containers within containers. Docker-in-Docker madness enabled.
  • Workspace trust dialog is pre-accepted automatically — no confirmation prompts on startup.
  • Two container types per workspace: claude-_path (interactive, with TTY), claude-_path_prog (programmatic, no TTY). Programmatic runs without TTY so they work from scripts, cron jobs, and other tools.
  • ~/.claude/bin is in PATH inside the container. Drop custom scripts there and they're available in every session.

📜 License

WTFPL – do what the fuck you want to.

About

claude but dockerized, goth-approved, and dangerously executable. This container gives you the Claude Code in a fully isolated ritual circle – no cursed system installs required.

Topics

Resources

License

Stars

Watchers

Forks

Contributors