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.
Because installing things natively is for suckers. This image is for devs who live dangerously, commit anonymously, and like their AI tools in containers.
- 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.mdin 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(-pis 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
- Docker installed and running
There's an install script that sets everything up automatically:
curl -fsSL https://raw.githubusercontent.com/psyb0t/docker-claude-code/master/install.sh | bashTo 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 | bashOr if you prefer manual control:
mkdir -p ~/.claudeIf 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.
| 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) |
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.
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"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"# 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 | bashclaudeStarts 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-updateProgrammatic runs never auto-update.
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.
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 versionIf not specified, the model defaults based on your account type (Max/Team Premium → Opus, Pro/Team Standard → Sonnet).
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 → (assistant → user)× repeated per tool call → rate_limit_event between turns → final assistant text → result.
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 PATHScripts 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.shUseful for installing extra packages, configuring services, or any one-time setup that should survive container restarts but re-run on fresh containers.
- 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/projectstays/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/binis in PATH inside the container. Drop custom scripts there and they're available in every session.
WTFPL – do what the fuck you want to.