The sandbox in Claude Code restricts shell commands. That is a real boundary, but a narrow one: the file-editing tools, MCP servers and hooks still run on your machine. A dev container moves the boundary outward. The whole Claude Code process lives inside a Docker container your editor connects to — on your computer or in a cloud service such as GitHub Codespaces — and your edits reach the repository through a mount.
This is the fifth post in a series on the Claude Code: Pro Level course, based on the elective lesson Claude Code in a dev container. If the sandbox is new to you, start with the sandbox lesson — the container builds on it.
Three layers
Each layer builds on the one before, and you can stop at whichever one you need:
| Layer | What it gives you |
|---|---|
| The Claude Code feature | the CLI inside any dev container |
A named volume at ~/.claude | your login survives a rebuild |
| The reference container | a regular user and a firewall — a place for running without prompts |
Layer 1: the feature
One line in .devcontainer/devcontainer.json adds Claude Code to a container:
{
"image": "mcr.microsoft.com/devcontainers/base:ubuntu",
"features": {
"ghcr.io/devcontainers/features/node:1": { "version": "lts" },
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
},
"postCreateCommand": "sudo apt-get update && sudo apt-get install -y bubblewrap socat"
}
The catch is the :1.0 tag. It pins the feature’s install script, not Claude Code. The feature installs the latest release, which then updates itself. And postCreateCommand is there for a reason: without bubblewrap and socat, the Bash sandbox does not work on Linux.
Layer 2: a login that survives a rebuild
Sign in to Claude Code, rebuild the container, and claude auth status returns code 1. A rebuild keeps the project folder but throws away the home folder, and ~/.claude with it. Codespaces behaves the same way: after Rebuild Container, /workspaces is still there and your login is not.
The fix is a Docker named volume mounted at ~/.claude, plus the environment variable CLAUDE_CONFIG_DIR pointing to the same path. The volume lives separately from the container, so the login and settings inside it outlast a rebuild:
"mounts": [
"source=claude-code-config-${devcontainerId},target=/home/node/.claude,type=volume"
],
"containerEnv": {
"CLAUDE_CONFIG_DIR": "/home/node/.claude"
}
Login still disappearing? Check that the volume’s target and CLAUDE_CONFIG_DIR name exactly the same path.
Layer 3: the reference container
The Claude Code repository ships a reference .devcontainer/ of three files: devcontainer.json, Dockerfile and init-firewall.sh. It adds two things:
- a regular user,
node, instead of root; - a firewall: outbound traffic is allowed only to the addresses listed in
init-firewall.sh.
This is the container the documentation supports for runs with --dangerously-skip-permissions. Running as root, the CLI refuses that flag outright.
Before the first build:
- Read all three files.
init-firewall.shruns as root on every container start, as thepostStartCommand. - Add the login domains. Next to
api.anthropic.com, the list needsclaude.aiandplatform.claude.com, or signing in and refreshing the login will hit the firewall. Need PyPI or a private package registry? Add it and rebuild. - Pin the version. Replace
latestin theCLAUDE_CODE_VERSIONbuild argument with a specific version, and add"DISABLE_AUTOUPDATER": "1"tocontainerEnv.
To check the firewall, make two requests from inside the container: one to example.com should fail, one to api.github.com should go through.
No prompts, but with limits
Once all of that checks out, one unattended run inside the container — with the same limits the course teaches in unattended runs with limits:
claude -p "Add a one-line description of the repository at the top of README.md" \
--dangerously-skip-permissions --model sonnet --max-turns 10
A cheaper model, a cap on turns, a small task. The container is the reason you may skip prompts here; it is not a reason to skip the limits.
What the container does not protect
A container does not stop the project from taking everything available inside it, including the Claude Code credentials in ~/.claude. The Dev Containers extension in VS Code also forwards your Git credentials and SSH agent, and the reference firewall allows GitHub, so a run can push in your name. Use containers only with repositories you trust, never mount ~/.ssh or cloud key files into one, and after a run without prompts read .git/config, the list of .git/hooks and the .devcontainer diff before you rebuild or commit.
That last check matters for the same reason the hooks post tells you to read a repository’s hooks before trusting it: code that runs automatically runs with whatever rights it was given.
Cleaning up
The volume outlives the container, and so does the login inside it. When you are finished, run claude auth logout inside the container, then delete the container and the volumes whose names start with claude-code-config- and claude-code-bashhistory- (docker volume ls, then docker volume rm). Delete a codespace separately: even stopped, it still takes up space.
Frequently asked questions
Does the :1.0 tag pin the Claude Code version?
No. It pins the feature’s install script. The feature installs the latest Claude Code, which then updates itself. To pin a version, use the reference container’s CLAUDE_CODE_VERSION build argument and turn off the autoupdater.
Why does Claude Code ask me to sign in again after a rebuild?
A rebuild discards the home folder, and ~/.claude with it. Mount a named volume at ~/.claude and point CLAUDE_CONFIG_DIR to the same path.
Can I run --dangerously-skip-permissions as root?
No. As root, the CLI does not accept that flag. The reference container runs as the regular user node.
Go deeper
The dev container lesson has a practice terminal where you write a devcontainer.json with the feature, a named volume and a matching CLAUDE_CONFIG_DIR. It is the quickest way to get the second layer right before your first rebuild wipes a login.