Run Claude Code inside a Docker container with a network proxy. A Squid forward proxy restricts internet access to only the services Claude needs (Anthropic API, GitHub, package registries), so Claude can work autonomously without risking unintended network access.
| Setup | OS | Best for |
|---|---|---|
| Terminal (CLI) | Linux, macOS, or Windows (WSL2) | Developers who prefer the command line |
| VS Code Dev Container | Linux, macOS, or Windows (WSL2) | Developers who use VS Code |
Windows users: WSL2 is required. See WSL2 setup below before proceeding.
Works on: Linux, macOS
This installs a claude-docker command that runs Claude Code inside a container from any terminal.
- Docker -- If you have WSL2 and Docker Desktop, this is already covered. Linux install guide or macOS Docker Desktop
- git and jq -- usually pre-installed on macOS; on Linux:
sudo apt install -y git jq
After installing Docker on Linux, run sudo usermod -aG docker $USER and log out/in so you can run Docker without sudo.
Verify Docker is working:
docker run hello-worldgit clone <this-repo>
cd claudecode-docker
./install.shIf the installer says ~/.local/bin is not in your PATH, add it:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrcNavigate to any project directory and run:
claude-dockerThe first run builds the Docker image, which takes a few minutes. After that, subsequent runs start in seconds. You'll be prompted to log in to your Claude Code account on first use -- see Logging in below.
OAuth login has to run with --no-firewall:
claude-docker --no-firewall
# /login inside Claude, complete the browser flow, exitThen start normally again (claude-docker). Tokens are stored in ~/.claude/.claude.json and persist across restarts.
Why: Claude Code's login opens a browser on your host that calls back to a local server. That callback needs the container to share the host's network namespace (--no-firewall uses --net=host), and it also needs localhost to mean "this container's loopback" rather than the remapped "host machine" used in normal mode. Outside of login, localhost in the container points at your host machine so MCP servers running locally work with the same config you'd use outside Docker.
claude-docker # Start Claude Code (with proxy)
claude-docker --no-firewall # Start without proxy/firewall
claude-docker --rebuild # Force rebuild the Docker image
claude-docker bash # Drop into a bash shell inside the containerAny additional arguments are passed through to claude. For example:
claude-docker --allow-dangerously-skip-permissionsSet CLAUDE_DOCKER_ALLOW_DOMAINS to a whitespace- or comma-separated list of domains. They are appended to the proxy allowlist when the container starts (no image rebuild required). Use a leading dot to match all subdomains.
export CLAUDE_DOCKER_ALLOW_DOMAINS=".internal.example.com api.partner.io"
claude-dockerDomains added this way are not reflected in /etc/claude-code/container.md, so Claude won't automatically know they're allowed. Mention them in your prompt if relevant.
Set CLAUDE_DOCKER_CUSTOMIZE to the path of a shell script. The script is copied into the build context and runs as root in the final layer of the Dockerfile, so it can install apt packages, write to /etc/, drop binaries in /usr/local/bin, etc. Build runs without the proxy, so direct network access is available.
export CLAUDE_DOCKER_CUSTOMIZE=~/.config/claude-docker/setup.sh
claude-dockerExample setup.sh:
#!/bin/bash
set -e
apt-get update && apt-get install -y --no-install-recommends fzf
# Install something for the unprivileged user:
su - "$LOCAL_USER" -c 'uv tool install httpie'The image rebuilds automatically when the script's contents change. Unset the variable (or unset CLAUDE_DOCKER_CUSTOMIZE) to go back to the stock image on the next run.
- Your current directory is mounted into the container, so Claude can read and edit your files directly.
- A Squid proxy filters outbound traffic by domain name, only allowing approved services.
- Direct outbound connections from Claude are blocked by iptables; all traffic must go through the proxy.
- Container instructions are injected into Claude's context on every session start via a managed hook, so Claude is aware of the proxy restrictions.
~/.claudeand~/.claude.jsonare mounted so your session, settings, and API keys persist.- Files are mounted read-write. Claude can modify files in your current directory. Use version control.
Works on: Linux, macOS, Windows (WSL2)
This sets up a dev container in your project. VS Code runs inside the container with the Claude Code extension pre-installed. No terminal setup needed -- everything is handled by VS Code.
- Docker Desktop -- macOS or Linux. Windows users: install Docker Desktop for Windows and enable the WSL 2 backend.
- VS Code with the Dev Containers extension
- git -- pre-installed on macOS/Linux; on WSL2:
sudo apt install -y git
Verify Docker is working:
docker run hello-worldClone this repo and run the install script, passing your project directory:
git clone <this-repo>
cd claudecode-docker
./install-devcontainer.sh /path/to/your/project- Open your project in VS Code
- VS Code will detect the
.devcontainer/folder and show a notification: "Reopen in Container" -- click it - Alternatively, press
Ctrl+Shift+P(orCmd+Shift+Pon macOS) and select Dev Containers: Reopen in Container
The first build takes a few minutes. After that, reopening is fast. The proxy and firewall initialize automatically when the container starts.
- Your workspace is mounted at
/workspaceinside the container. - Your Claude config is mounted from your home directory.
~/.claudeand~/.claude.jsonare shared between host and container, so your session and settings persist across rebuilds.
WSL2 (Windows Subsystem for Linux) is required for running this project on Windows.
- Install WSL2 by following the official Microsoft documentation.
- Install Docker Desktop for Windows and enable the WSL 2 backend in its settings.
Once set up, run all commands from your Ubuntu terminal, not PowerShell. Follow Option 1 or Option 2 above.
For best performance, keep your projects inside the WSL2 filesystem (e.g.
~/projects/) rather than/mnt/c/.
Line endings: Git on Windows defaults to rewriting line endings to CRLF on checkout (
core.autocrlf=true). If you use a Windows Git client (GitKraken, Visual Studio, Windows Git Bash) and access the same checkout from WSL2, shell scripts and tools inside the container will break due to unexpected\rcharacters. The proper fix is to add a.gitattributesfile to each repo:* text=auto eol=lfThis forces LF endings on checkout for everyone, regardless of local git config. Modern Visual Studio and GitKraken both handle LF fine on Windows. Without this, your only options are cloning from within WSL2 (separate checkout from your Windows tools) or setting
core.autocrlf=falseglobally in your Windows Git config.
A Squid forward proxy runs inside the container. All HTTP/HTTPS requests from Claude go through the proxy, which only allows approved domains. Direct outbound connections are blocked by iptables using owner matching -- the proxy process runs as a different user than Claude, so iptables can distinguish their traffic.
On every container start, the init script verifies that:
- Allowed domains are reachable through the proxy
- Blocked domains are rejected by the proxy
- Direct connections (bypassing the proxy) are blocked by iptables
Claude is informed about the container environment via a managed SessionStart hook that injects /etc/claude-code/container.md into Claude's context. This file is generated at build time and includes the proxy allowlist.
| Service | Domains | Why |
|---|---|---|
| Anthropic | .anthropic.com, .statsig.com, .sentry.io |
Claude Code API and telemetry |
| GitHub | .github.com, .githubusercontent.com |
Git operations, downloading releases |
| npm | .npmjs.org |
Node.js packages |
| PyPI | .pypi.org, .pythonhosted.org, .astral.sh |
Python packages |
| Crates.io | .crates.io |
Rust packages |
| Rust toolchain | .rust-lang.org, .rustup.rs |
Rust installer |
| Go | .golang.org, storage.googleapis.com |
Go modules |
| NuGet | .nuget.org, nugetserver-api.ft.dibk.no |
.NET packages (incl. DIBK feed) |
| VS Code | .visualstudio.com, vscode.blob.core.windows.net |
Extensions and updates (dev container mode) |
| Ubuntu APT | .ubuntu.com, ftp.uninett.no |
System package installation via apt-safe |
| MCP servers | mcp.exa.ai, mcp.context7.com, instances-mcp.vantage.sh |
Remote MCP services |
The image is based on Ubuntu 24.04 and includes:
- Node.js 24 (via nvm)
- Python 3 with uv
- Go 1.25
- Rust (via rustup)
- .NET (latest SDK)
- Playwright with Chromium
- Common tools: git, ripgrep, jq, tree, postgresql-client
- Terraform MCP server
apt-safeCLI for safe package installation
The container includes apt-safe, a hardened wrapper around apt that the agent can use to install system packages during a session. Installed packages do not persist across container restarts.
- First build is slow. The image includes many language runtimes (~3-4 GB). Subsequent runs reuse the cached image.
--no-firewalldisables all network restrictions (CLI only). Use this if you need access to services not on the allow list, but be aware there is no network sandbox.