Run Claude Code in a Docker container: a pinned, non-root image with persistent login, plus an opt-in egress firewall for running --dangerously-skip-permissions without handing the agent your whole machine.
Anthropic ships a reference devcontainer for VS Code and calls it "a working example rather than a maintained base image". This repo is the plain-Docker version of that idea: no editor required, no wrapper CLI to install, just a readable Dockerfile and a compose file you can copy into any project.
Container behavior below (build, non-root flag handling, firewall allow/block, login persistence, version pinning) was verified by building and running this exact image (Claude Code 2.1.235, August 2026); CLI flag and output semantics are per the official docs, linked throughout.
- Autonomous mode needs a boundary. Anthropic's own guidance is to run
--dangerously-skip-permissionssessions inside a container, VM, or sandbox runtime, so file tools, subprocesses, MCP servers, and hooks are all inside the boundary (sandbox environments). Inside the container, a misstep trashes/workspace, not your home directory, SSH keys, or other projects. - The CLI enforces part of this itself: it refuses
--dangerously-skip-permissionswhen running as root. This image runs Claude Code as an unprivilegedclaudeuser, so the flag works. - Reproducibility. The image pins a Claude Code version and disables the auto-updater. Every teammate and every CI run gets the same agent.
- Parallelism. Containers are cheap: run several isolated sessions against different checkouts at once.
git clone https://github.com/agent37-platform/claude-code-docker.git
cd claude-code-docker
docker compose run --rm claudeThat drops you into an interactive Claude Code session with the current directory mounted at /workspace. Log in once (subscription OAuth or API key); credentials land in a named volume (claude-config), so every later run and rebuild stays logged in.
Work on a real project instead of this repo:
WORKSPACE=~/code/myapp docker compose run --rm claudePrefer raw docker?
docker build -t claude-code-docker .
docker run --rm -it \
-v claude-config:/home/claude/.claude \
-v "$PWD:/workspace" \
claude-code-dockerPin a different release with --build-arg CLAUDE_CODE_VERSION=2.1.227, and match your host user with --build-arg UID=$(id -u) --build-arg GID=$(id -g) if files created in /workspace come out with the wrong owner (Linux hosts; Docker Desktop on macOS/Windows maps ownership for you). On SELinux-enforcing hosts (Fedora/RHEL, podman), add :z to the workspace mount ("$PWD:/workspace:z") or the container cannot read your project.
All the documented options work here (auth docs):
| Method | How | Best for |
|---|---|---|
| Interactive OAuth | Run claude, follow the login flow; if the browser callback cannot reach the container, paste the code at the prompt |
Pro/Max subscribers, daily use |
ANTHROPIC_API_KEY |
export it before docker compose run, or put it in .env |
API billing, headless and CI |
CLAUDE_CODE_OAUTH_TOKEN |
Generate once with claude setup-token (any logged-in machine), then set it like an API key |
Subscription auth in CI and scripts |
| Gateway / cloud | ANTHROPIC_AUTH_TOKEN, or CLAUDE_CODE_USE_BEDROCK / CLAUDE_CODE_USE_VERTEX with that provider's credentials |
Enterprise setups |
Two container-specific gotchas the docs bury:
- Persisting login across rebuilds. OAuth account state lives in
~/.claude.json, which is next to, not inside,~/.claude. This image setsCLAUDE_CONFIG_DIR=/home/claude/.claudeso ALL state lives in one directory, and one volume mount survives everything. If you change the volume target, changeCLAUDE_CONFIG_DIRwith it (reference). --baremode never reads OAuth credentials. In scripts using--bare(recommended for CI: skips hooks, plugin sync, MCP autodiscovery, and CLAUDE.md auto-discovery), authenticate withANTHROPIC_API_KEY; aCLAUDE_CODE_OAUTH_TOKENwill be ignored there.
Never mount host secrets (~/.ssh, ~/.aws, your real ~/.gitconfig with credential helpers) into a container that runs with permissions off. Anything inside the boundary is readable by whatever the agent executes, including its own credentials volume. Give the container copies of exactly what the task needs.
docker compose run --rm yoloThis starts claude --dangerously-skip-permissions behind a default-deny egress firewall (init-firewall.sh, same approach as Anthropic's reference devcontainer): only the domains Claude Code needs per the network docs (Anthropic's API, claude.ai and platform.claude.com for sign-in and OAuth token refresh, registry.npmjs.org), GitHub's published IP ranges, and the telemetry hosts from Anthropic's reference firewall are reachable. Everything else is dropped before it leaves the container. Verified behavior of this exact image: github.com connects, google.com times out.
Your project needs more endpoints? Allow them per run, no file edits:
FIREWALL_ALLOW_DOMAINS="pypi.org files.pythonhosted.org" docker compose run --rm yoloHonest limits, so you can decide what to trust it with: DNS stays open (the allowlist has to resolve, so DNS tunneling remains an exfil channel), allowlisted hosts like GitHub are themselves reachable, the allowlist IPs are a startup snapshot (a long session can lose an endpoint if a CDN rotates IPs; restart the container to re-resolve), and a malicious repo can still read anything inside the container. The firewall shrinks the blast radius; it does not make untrusted code safe. For hostile-code review, use a throwaway checkout, no credentials volume, and a short-lived token.
The image runs Claude Code's print mode (headless docs) exactly as documented:
docker run --rm -i \
-e ANTHROPIC_API_KEY \
-v "$PWD:/workspace" \
claude-code-docker \
claude -p "Explain the failing test in tests/api.test.ts" --bare --output-format json--output-format json returns the result plus session_id and total_cost_usd; stream-json emits NDJSON events for pipelines; add --max-turns N and --allowedTools "Read,Grep" to bound what an unattended run may do. A ready-to-copy GitHub Actions workflow is in examples/github-actions.yml.
By August 2026 there are several ways to isolate Claude Code. They solve different problems:
| Option | What it is | Reach for it when |
|---|---|---|
| This repo | Plain pinned Dockerfile + compose, firewall included | You want docker run/CI/server usage with no editor or extra CLI dependency |
| Anthropic devcontainer | Reference .devcontainer + official Feature |
You live in VS Code or Codespaces and want the editor-integrated version of the same idea |
| Docker Sandboxes | Docker's free standalone microVM product (sbx run claude), no Docker Desktop needed |
You want managed per-session microVMs rather than your own image |
| Native sandboxing | Built-in OS-level Bash sandbox (Seatbelt/bubblewrap) + @anthropic-ai/sandbox-runtime |
You want OS-level guardrails without any container, or layered inside one |
| claudebox, claude-pod and friends | Community wrapper CLIs around the same container idea | You want their extras (language profiles, per-project images) and accept a wrapper layer |
These compose: the native Bash sandbox can run inside this container for defense in depth (unprivileged containers need the nested-sandbox setting, see the troubleshooting note).
A container on your laptop dies with your laptop. The same image this repo builds can run 24/7 on Agent37 Cloud, which hosts any public Docker image as a persistent instance: from $1.99/mo shared, or $4.94/mo dedicated for an always-on 2 vCPU / 4 GB box with 6 GB disk (the default 4 GB disk works out to $4.76/mo), metered per minute, delete anytime. New workspaces get a $1 starter credit, so the first instance is free to try, no card required.
# 1. Build for amd64 (Agent37 runs linux/amd64) and push anywhere public
docker build --platform linux/amd64 -t ghcr.io/you/claude-code-docker:v1 .
docker push ghcr.io/you/claude-code-docker:v1
# 2. Register it as a template (API key: dashboard -> Cloud -> API keys)
curl -X POST https://api.agent37.com/v1/templates \
-H "Authorization: Bearer $AGENT37_API_KEY" -H "Content-Type: application/json" \
-d '{ "name": "claude-code", "image_ref": "ghcr.io/you/claude-code-docker:v1" }'
# 3. Provision an always-on instance from it
curl -X POST https://api.agent37.com/v1/instances \
-H "Authorization: Bearer $AGENT37_API_KEY" -H "Content-Type: application/json" \
-d '{ "template": "claude-code", "env": { "ANTHROPIC_API_KEY": "sk-ant-..." } }'
# 4. Drive it: run headless turns over the exec API, from your app or your own cron
curl -X POST https://api.agent37.com/v1/instances/<id>/exec \
-H "Authorization: Bearer $AGENT37_API_KEY" -H "Content-Type: application/json" \
-d '{ "command": "claude -p \"Triage new GitHub issues in /workspace\" --output-format json --max-turns 20" }'Exec calls are capped at 280 seconds: for longer agent runs, start the turn in the background (nohup claude -p ... > /tmp/out.json &) and collect the file with a second exec.
One note for that flow: an instance needs a long-running main process, and this image's default claude exits without a terminal. Register with a keep-alive command instead; a two-line child image does it:
FROM ghcr.io/you/claude-code-docker:v1
CMD ["sleep", "infinity"]Full walkthrough (cloud builds without a registry, budgets): Agent37 custom image docs. We run Agent37; a one-click Claude Code template is coming, and this repo is the exact image path that page describes today.
| File | Purpose |
|---|---|
Dockerfile |
debian-slim + pinned official installer, non-root user, CLAUDE_CONFIG_DIR set, auto-updater off |
docker-compose.yml |
claude (interactive) and yolo (firewalled autonomous) services, persistent auth volume |
init-firewall.sh |
Default-deny egress allowlist (Anthropic + GitHub + npm), extensible via env var |
entrypoint.sh |
Raises the firewall when FIREWALL=1, then execs your command |
examples/github-actions.yml |
Headless Claude Code in CI |
.env.example |
Every knob, documented |
No Node.js in the image: the official native installer ships a self-contained binary with no Node.js runtime dependency, so the base stays small (debian:bookworm-slim + git, ripgrep, curl, and the firewall tools). Add your project's toolchain in a child image (FROM claude-code-docker + your apt-get install).
MIT for the files in this repo. Not affiliated with Anthropic; Claude Code is Anthropic's product, installed here via the official installer, and any image you build and publish contains their binary under their terms.