Skip to content
zero2vibecodelearn vibe coding
Claude Code: Pro LevelBy Mikhail Kuzmitskii

Claude Code in a dev container: three layers, and what they do not protect

A dev container holds the whole Claude Code process, not just its shell commands. Add the CLI with one feature line, keep your login across rebuilds with a named volume, and save runs without prompts for the reference container with a firewall and a regular user.

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.

A robot working at a desk inside a glass cube standing on a wooden dock by the sea

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:

LayerWhat it gives you
The Claude Code featurethe CLI inside any dev container
A named volume at ~/.claudeyour login survives a rebuild
The reference containera 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:

  1. Read all three files. init-firewall.sh runs as root on every container start, as the postStartCommand.
  2. Add the login domains. Next to api.anthropic.com, the list needs claude.ai and platform.claude.com, or signing in and refreshing the login will hit the firewall. Need PyPI or a private package registry? Add it and rebuild.
  3. Pin the version. Replace latest in the CLAUDE_CODE_VERSION build argument with a specific version, and add "DISABLE_AUTOUPDATER": "1" to containerEnv.

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

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.

Read next

Try it hands-on

Free interactive courses on this topic — in your browser, from zero.