Skip to content

Development containers

Run Claude Code inside a dev container so every engineer gets the same isolated toolchain, with persistent login, baked-in policy and optional egress limits.

A dev container describes a development environment as code: a Docker image, the tools in it and the editor settings around it. Put Claude Code inside one and every command Claude runs executes in the container rather than on your laptop, while file edits still land in your local checkout through the bind mount.

I use dev containers for client work where I want a clean, repeatable toolchain and a bit of distance between Claude and my machine. This page covers installing Claude Code in a container, keeping the login across rebuilds, applying organisation policy, restricting network access and running without prompts.

Warning: A container is a strong boundary, not a perfect one. If you run with --dangerously-skip-permissions, a malicious repository can exfiltrate anything inside the container, including the Claude Code credentials in ~/.claude. Only do this with repositories you trust, keep an eye on what Claude is doing, and do not mount host secrets such as ~/.ssh or cloud credential files. Prefer short-lived or repository-scoped tokens.

How the pieces fit

The container runs on Docker locally or on a cloud host such as GitHub Codespaces. An editor that supports the Dev Containers spec (VS Code, Codespaces, JetBrains IDEs, Cursor) connects to it. You browse and edit as normal, but the terminal, language servers and build tools all run inside the container. Editors without dev container support, plain Vim for instance, are outside this workflow.

Claude Code sits alongside your toolchain in the container and sees the same files and dependencies. In VS Code you can use the extension panel or claude in the integrated terminal; both run in the container and share one ~/.claude.

Adding Claude Code

The simplest route is the official Claude Code Dev Container Feature, published at ghcr.io/anthropics/devcontainer-features/claude-code. It works with any Dev Containers-compatible tool, and in VS Code or Codespaces it also installs the Claude Code extension.

1. Add the feature

Here is a .devcontainer/devcontainer.json for a Python service, adding Claude Code next to the Python feature:

{
  "name": "orders-api",
  "image": "mcr.microsoft.com/devcontainers/python:3.12",
  "features": {
    "ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {}
  },
  "remoteUser": "vscode"
}

The :1.0 tag pins the feature's install script, not the Claude Code version. The feature installs the latest release, and Claude Code then auto-updates inside the container unless you stop it (see policy).

The feature installs Node.js if the image lacks it. If the build fails with Failed to install Node.js and npm, add "ghcr.io/devcontainers/features/node:1": {} above the Claude Code feature and rebuild.

2. Rebuild

In VS Code, open the Command Palette (Cmd+Shift+P on Mac, Ctrl+Shift+P elsewhere) and run Dev Containers: Rebuild Container. Codespaces, JetBrains and the Dev Containers CLI each have their own rebuild action.

3. Sign in

Open a terminal in the container and run claude.

  • Anthropic accounts sign in through the browser.
  • Bedrock, Vertex AI or Foundry use cloud credentials with no browser step. Pass them in as environment variables via containerEnv, a Codespaces secret or workload identity, rather than mounting credential files. See Amazon Bedrock, Google Vertex AI or Microsoft Foundry.

If the browser login finishes but the callback never reaches the container (port forwarding sometimes fails to route it), copy the code the browser shows and paste it at Paste code here if prompted.

Keeping the login across rebuilds

Rebuilding throws away the container's home directory, so by default everyone signs in again every time. Two things need to survive: the ~/.claude directory (token, settings, history) and ~/.claude.json, which lives outside that directory and holds your OAuth account, personal MCP servers and project trust. Mounting a volume at ~/.claude on its own is not enough.

The fix is to mount a named volume at ~/.claude and set CLAUDE_CONFIG_DIR to the same path, so .claude.json is written inside the volume too:

"mounts": [
  "source=claude-state-${devcontainerId},target=/home/vscode/.claude,type=volume"
],
"containerEnv": {
  "CLAUDE_CONFIG_DIR": "/home/vscode/.claude"
}

Use your remoteUser's home directory in place of /home/vscode, and merge into any existing containerEnv rather than adding a second one. Including ${devcontainerId} in the volume name gives each project its own state; drop it if you want one shared login across repositories.

Codespaces keeps ~/.claude when you stop and start a codespace but clears it on rebuild, so the same configuration helps there. To carry authentication across codespaces, store ANTHROPIC_API_KEY or a CLAUDE_CODE_OAUTH_TOKEN (from claude setup-token; see authentication) as a Codespaces secret, which appears as an environment variable in the container.

Baking in organisation policy

Because every engineer runs the same image, a dev container is a handy place to apply policy. On Linux, Claude Code reads /etc/claude-code/managed-settings.json at the top of the settings hierarchy, so copy one in from your Dockerfile:

COPY .devcontainer/claude-policy.json /etc/claude-code/managed-settings.json

Be honest about what that achieves: anyone with write access to the repository can edit or remove that line. For policy engineers cannot sidestep, use server-managed settings or MDM. Managed settings lists the keys and delivery routes.

Environment variables for every session go in containerEnv. This turns off telemetry and error reporting and stops auto-update:

"containerEnv": {
  "CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC": "1",
  "DISABLE_AUTOUPDATER": "1"
}

Note that CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC also blocks feature-flag fetching, so Remote Control and other flag-dependent features stop working in the container.

Pinning a version. The feature always installs the latest release. For reproducible builds, skip the feature and install a specific version in your Dockerfile with npm install -g @anthropic-ai/claude-code@<version>, then set DISABLE_AUTOUPDATER=1.

MCP servers. Define them at project scope in a .mcp.json at the repository root so they travel with the container configuration. Install any binaries that local stdio servers need in the Dockerfile, and add remote servers' domains to your egress allowlist. See MCP.

For the full set of controls (permission rules, tool restrictions, MCP allowlists) see organisation setup.

Restricting network egress

You can limit the container to the destinations Claude Code needs. Network configuration lists the required hosts, and data usage covers the optional telemetry connections and how to switch them off.

Anthropic's reference container (in the .devcontainer folder of the anthropics/claude-code GitHub repository) includes an init-firewall.sh script that allows only listed destinations. Running a firewall inside a container needs extra capabilities, so the reference adds NET_ADMIN and NET_RAW via runArgs:

"runArgs": ["--cap-add=NET_ADMIN", "--cap-add=NET_RAW"],
"postStartCommand": "sudo /usr/local/bin/init-firewall.sh"

None of this is required for Claude Code itself; if your network already controls egress, leave it out.

Running without permission prompts

Because the container runs Claude Code as a non-root user and confines commands to the container, it is a reasonable place for --dangerously-skip-permissions and unattended runs. The flag is refused when running as root, so make sure remoteUser is a normal account.

Remember what you are giving up. Claude can still change any file in the bind-mounted workspace, which is your real checkout on the host, and reach anything the network policy allows. Pair the flag with egress restrictions.

If you just want fewer prompts, auto mode is usually the better choice, since a classifier still reviews each action. Admins who want to forbid the bypass flag entirely can set permissions.disableBypassPermissionsMode to "disable" in managed settings.

Trying the reference container

To see a complete, hardened setup before building your own:

  1. Install VS Code and the Dev Containers extension.
  2. Clone the anthropics/claude-code repository from GitHub and open it in VS Code.
  3. Choose Reopen in Container (or run Dev Containers: Reopen in Container).
  4. When the build finishes, open a terminal with Ctrl+` and run claude.

It combines the CLI, the egress firewall, persistent volumes and a Zsh shell. It is an example rather than a maintained base image. The three files that matter:

FileDoes
devcontainer.jsonVolume mounts, runArgs capabilities, VS Code extensions, containerEnv
DockerfileBase image, development tools and the Claude Code install
init-firewall.shRestricts outbound traffic to an allowlist

Copy the .devcontainer/ folder into your project and adjust the Dockerfile for your toolchain, or just add the feature to the container you already have.