Run Claude Code in a sandboxed Docker container.
How it works: claudebox is a Bash wrapper around a single docker run — no daemon, no background process. Each time you run it, the script:
- Splits the arguments — its own flags (
--docker,--ssh,-p,--build, …) are handled locally; everything else is passed straight through toclaudein the container. - Ensures the image exists — builds it on first run (
docker build), reuses it after;--buildforces a rebuild. - Assembles the
docker run, mainly:- mounts your project at its real host path (not
/workspace) and works there, so file paths and~/.claude/projects/<path>session files line up with a nativeclauderun; - mounts
~/.claude(and~/.claude.json) so config, memory, and auth persist — on macOS it reads your Keychain token and forwards it in as an env var; - by default adds
--cap-add=NET_ADMINplus an env flag that tells the container to firewall off your LAN;-p,--ssh, and--docker/--host-dockeradd ports, the SSH agent, and Docker access; - allocates a TTY so the session is interactive.
- mounts your project at its real host path (not
- Hands off to the container's entrypoint, which installs the LAN firewall, then drops
NET_ADMINbefore launching Claude (so Claude can't undo it), optionally starts a rootless Docker daemon, and finallyexecsclaudewith your passed-through arguments.
Net effect: the same Claude on your real files, with config/memory persisted, the network sandboxed to internet-only, and Docker off unless you opt in — isolated enough to run --dangerously-skip-permissions. See Sandboxing for specifics.
Note (macOS): If you use a Claude subscription (Pro/Max), your auth token lives in the macOS Keychain, which the container can't read directly. claudebox reads it and forwards it into the container automatically, so auth just works. If that ever fails (you'll see a warning), run
/loginonce inside the container — the token is then saved to~/.claudeand persists across sessions.
- Docker installed and running
Each release is published to GitHub Container Registry for linux/amd64 and linux/arm64, so you can skip the local build. You still need the claudebox wrapper script (see Setup below) — the prebuilt image just saves it from building one.
Pull a release and tag it as the local image the wrapper looks for:
docker pull ghcr.io/startswithaj/claudebox:latest
docker tag ghcr.io/startswithaj/claudebox:latest claudeboxNow run claudebox as usual — it finds the local claudebox image and won't rebuild. Pin a specific version with a tag like :v1.0.0 instead of :latest. To update later, re-pull and re-tag.
The prebuilt image includes all optional languages (Go, Python, Rust). For a smaller image — or to go back to building from source — run
claudebox --build(see Custom builds).
- Clone the repo:
git clone https://github.com/youruser/claudebox.git- Add it to your PATH (pick one):
# bash
echo 'export PATH="$PATH:$HOME/claudebox"' >> ~/.bashrc
source ~/.bashrc
# zsh
echo 'export PATH="$PATH:$HOME/claudebox"' >> ~/.zshrc
source ~/.zshrcAdjust the path if you cloned it somewhere other than $HOME/claudebox.
- (Optional) Alias
claudetoclaudebox:
# bash
echo 'alias claude="claudebox"' >> ~/.bashrc
source ~/.bashrc
# zsh
echo 'alias claude="claudebox"' >> ~/.zshrc
source ~/.zshrc# Just run it — the Docker image builds automatically on first launch
claudeboxAll arguments are passed through to Claude Code:
claudebox --resume
claudebox --print "explain this repo"
claudebox --model sonnetTip: While Claude's interactive UI is running, the terminal captures the mouse, so a normal click-drag won't select text. Hold Option (⌥) while dragging to select and copy in macOS terminals (iTerm2/Terminal.app); on most Linux terminals it's Shift.
Publish a port so you can reach a server Claude starts inside the container. For example, if Claude runs a web frontend on port 3000 in the container, map it out and open it in your browser:
claudebox -p 3000:3000
# then visit http://localhost:3000 on your machineThe format is -p HOST:CONTAINER. Repeat the flag for multiple ports — say a frontend and its API:
claudebox -p 3000:3000 -p 8080:8080Forward your local SSH agent into the container for git operations over SSH:
claudebox --sshRebuild the image at any time:
claudebox --buildOnly include specific languages to keep the image smaller:
claudebox --build --with go,python
claudebox --build --with rustAvailable tools: go, python, rust. All are included by default.
Node.js and npm are always included as they are required by Claude Code.
Claudebox flags and Claude flags can be mixed freely:
claudebox --ssh -p 3000:3000 --docker --resume| Flag | Description |
|---|---|
--docker |
Start an isolated rootless Docker daemon inside the container (safe; host Docker untouched) |
--host-docker |
Mount the host Docker socket — grants host-root power and escapes the sandbox; use only when you need the host's daemon |
--allow-lan |
Allow the container to reach your local network (the LAN firewall is on by default) |
--ssh |
Forward your local SSH agent into the container for git over SSH |
-p, --port <host:container> |
Publish a container port to your host so you can reach it — e.g. -p 3000:3000, then open http://localhost:3000 (repeatable) |
--build |
(Re)build the image before launching |
--with <list> |
With --build, include only these optional languages: go, python, rust |
--help, -h |
Show usage and exit |
Any other arguments are passed straight through to Claude Code (e.g. --resume, --model, --print, --dangerously-skip-permissions).
claudebox is built to run Claude — including in --dangerously-skip-permissions mode — without giving it a path to your local network or host machine.
By default, egress to private/LAN ranges (10/8, 172.16/12, 192.168/16, 169.254/16) is dropped inside the container, so Claude can reach the public internet but not other machines on your network, your router's admin page, or cloud metadata endpoints. DNS still works (the container's own resolvers are allowlisted).
The firewall is installed by the container's entrypoint, which then drops the CAP_NET_ADMIN capability before launching Claude — so Claude itself cannot flush or alter the rules, even running as root.
Allow LAN access if you actually need it (e.g. hitting a service on your host):
claudebox --allow-lanClaude has no access to Docker unless you opt in. Two modes:
# Isolated, rootless Docker daemon inside the container. Safe: it has its own
# image cache and containers, can't see or touch your host's Docker, and a
# breakout from a nested container lands in the sandbox, not on your host.
claudebox --docker
# Mount the host's Docker socket. DANGEROUS: this is effectively root on your
# host — it lets the container escape the sandbox and bypass the LAN firewall.
# Only use it when you specifically need the host's daemon (e.g. pushing to a
# local registry or driving the host's Kubernetes) and trust the session.
claudebox --host-docker--docker is the right choice for most "Claude needs to build/run a container" tasks. Reach for --host-docker only when the work genuinely targets the host's Docker.
The base image always includes:
- Node.js 24 + npm (required by Claude Code)
- Git, git-lfs, ssh
- Build tools (gcc, cmake, pkg-config)
- Search tools (ripgrep, fd, jq)
- Claude Code CLI
- Deno
- Rootless Docker (daemon + CLI, started on demand with
--docker)
Optional (included by default, configurable with --with):
- Go 1.24
- Python 3 + pip + venv
- Rust (stable via rustup)
test/run.sh builds a lean image and verifies the sandbox end to end — the firewall rules, the CAP_NET_ADMIN drop, internet egress, LAN blocking, and rootless Docker (--docker). Run it after changing the Dockerfile, the entrypoint, or the claudebox wrapper:
test/run.shIt exits non-zero if any check fails. On hosts that block the user-namespace mapping rootless Docker needs (e.g. some nested CI containers), the rootless test automatically falls back to --privileged and says so — normal claudebox --docker runs unprivileged.
