Claude Code in Docker Compose: Network-Isolated Setup (2026)
Claude Code in Docker Compose with network isolation — full docker-compose.yml, persistent Pro/Max auth, Squid egress allowlist, and per-project profiles. 2026 setup.
On this page ▾
Anthropic’s Claude Code CLI can run bash commands, install packages, and make network requests. That’s powerful — but a single bad prompt could reach places you don’t want it to. This guide shows how to install and run Claude Code in Docker Compose on Linux with full autonomy inside the container, while only allowing outbound traffic to an allowlist of domains.
You’ll end up with a standard Claude Code Docker image, a docker-compose.yml for orchestration, and an optional Squid proxy for domain-level network isolation. All four files fit in ~80 lines.
TL;DR — drop-in docker-compose.yml
Want to skip the explanation and start? This is the minimum viable docker-compose.yml. Save it next to a Dockerfile, entrypoint.sh, and squid.conf (full contents below) and run docker compose up -d proxy && docker compose run --rm agent -p "hello".
# docker-compose.yml — Claude Code in Docker Compose with network isolation
services:
proxy:
image: ubuntu/squid
volumes:
- ./squid.conf:/etc/squid/squid.conf:ro
networks: [isolated, internet]
restart: unless-stopped
agent:
build: .
depends_on: [proxy]
environment:
HTTP_PROXY: http://proxy:3128
HTTPS_PROXY: http://proxy:3128
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY:-}
volumes:
- ${WORKSPACE:-./workspace}:/workspace
- claude-config:/host-claude # named volume — persists Pro/Max auth across rebuilds
- claude-json:/host-claude-json
networks: [isolated]
profiles: [run] # docker compose up only starts proxy
volumes:
claude-config:
claude-json:
networks:
isolated: { internal: true } # no direct internet
internet: { driver: bridge } # only Squid touches this
What this gives you:
- Stock Claude Code CLI in a Linux container, no custom build.
- A Squid proxy on a dedicated network — agent can only reach allowlisted domains.
- Persistent OAuth auth via the named
claude-config/claude-jsonvolumes — sign in once on the host, copy the credential into the volume, and every subsequent rebuild keeps you logged in. - Multi-project isolation via Compose profiles (see Multi-project isolation below).
Read the full architecture below, or jump straight to The Files.
Quick start: install Claude Code CLI in Docker
If you just want a working container, here are the four files you need:
Dockerfile— extendsnode:20-slim, installs@anthropic-ai/claude-codevianpmentrypoint.sh— forwards your OAuth credentials into the containersquid.conf— Squid proxy allowlist for domain-level network isolation (optional but recommended)docker-compose.yml— wires them together
Jump to The Files for the contents, or read the next section first for the architecture.
Why isolate Claude Code CLI’s network access?
Claude Code is an agentic CLI. Given tool-use permission, it can curl arbitrary URLs, git push to remotes, ssh into hosts, pip install from any index, and rm -rf anything it has access to. Docker already solves the filesystem blast-radius problem (mount only the workspace), but network is the other half — an agent that can reach arbitrary domains can exfiltrate data, fetch attacker-controlled scripts, or send SSRF-style requests to your internal network.
A Squid proxy on a dedicated allowlist fixes this with three properties:
- No direct internet from the agent container. The Docker network is marked
internal: true, so the only way out is through the proxy. - Allowlist, not blocklist. You name the domains the agent is allowed to reach (Anthropic API, GitHub, npm registry, etc.). Everything else fails closed.
- Transparent to the agent. Claude Code sees standard
HTTP_PROXY/HTTPS_PROXYenvironment variables and respects them without any wrapper code.
Architecture
The setup has two pieces:
- A Squid proxy container that maintains the domain allowlist.
- A Claude Code container on an internal Docker network. All its traffic goes through the proxy.
┌─────────────────────────────────────────────┐
│ Host │
│ │
│ ┌────────────┐ ┌─────────────────────┐ │
│ │ Squid │◄───│ isolated network │ │
│ │ (allowlist)│ │ ┌───────────────┐ │ │
│ └─────┬──────┘ │ │ Claude Agent │ │ │
│ │ │ │ (ephemeral) │ │ │
│ internet │ └───────────────┘ │ │
│ network └─────────────────────┘ │
│ │ │
│ ▼ │
│ Internet (filtered) │
└─────────────────────────────────────────────┘
The agent can do whatever it wants inside /workspace. If it tries to reach example.com or any domain not on the allowlist, the proxy blocks it.
Prerequisites
- Linux (Debian/Ubuntu, Fedora, Arch — anything with a recent kernel). The setup also works on Docker Desktop for macOS and Windows.
- Docker Engine 24.0 or newer
- Docker Compose v2 (comes bundled with current Docker Desktop and the
docker-compose-pluginpackage) - An Anthropic API key or an existing
claudelogin on the host (the entrypoint forwards OAuth credentials from~/.claudeinto the container)
If you don’t have Docker yet:
# Debian / Ubuntu
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker "$USER" # log out / back in to pick up group membership
The files
You need four files in a single directory. Here’s each one.
Dockerfile — the Claude Code Docker image
FROM node:20-slim
RUN npm install -g @anthropic-ai/claude-code
RUN apt-get update && apt-get install -y git curl ca-certificates && rm -rf /var/lib/apt/lists/*
COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
USER node
WORKDIR /workspace
ENTRYPOINT ["entrypoint.sh"]
The node user in node:20-slim has UID 1000, which matches most Linux host users. Volume mounts work without permission tweaks.
entrypoint.sh — OAuth credential forwarding
Claude Code reads OAuth credentials from ~/.claude and ~/.claude.json. We mount the host’s credentials read-only, then copy them into the container so Claude has a writable home directory for session data.
#!/bin/bash
set -e
if [ -d "/host-claude" ]; then
mkdir -p "$HOME/.claude"
if [ -f "/host-claude/.credentials.json" ]; then
cp "/host-claude/.credentials.json" "$HOME/.claude/.credentials.json"
fi
for f in statsig.json settings.json; do
if [ -f "/host-claude/$f" ]; then
cp "/host-claude/$f" "$HOME/.claude/$f"
fi
done
fi
if [ -f "/host-claude.json" ]; then
cp "/host-claude.json" "$HOME/.claude.json"
fi
exec claude "$@"
squid.conf — the domain allowlist
Edit this to match what your agent needs access to.
acl allowed_sites dstdomain .anthropic.com
acl allowed_sites dstdomain .github.com
acl allowed_sites dstdomain .googleapis.com
acl allowed_sites dstdomain registry.npmjs.org
acl allowed_sites dstdomain .sentry.io
acl allowed_sites dstdomain .statsigapi.net
acl SSL_ports port 443
acl CONNECT method CONNECT
http_access allow CONNECT allowed_sites
http_access allow allowed_sites
http_access deny all
http_port 3128
Gotcha: Squid doesn’t allow overlapping entries. If you add .anthropic.com (leading dot covers all subdomains), don’t also add api.anthropic.com. Squid will refuse to start.
docker-compose.yml — wiring it together
services:
proxy:
image: ubuntu/squid
volumes:
- ./squid.conf:/etc/squid/squid.conf:ro
networks:
- isolated
- internet
restart: unless-stopped
agent:
build: .
depends_on:
- proxy
environment:
- HTTP_PROXY=http://proxy:3128
- HTTPS_PROXY=http://proxy:3128
- http_proxy=http://proxy:3128
- https_proxy=http://proxy:3128
- ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY:-}
volumes:
- ${WORKSPACE:-./workspace}:/workspace
- ${HOME}/.claude:/host-claude:ro
- ${HOME}/.claude.json:/host-claude.json:ro
networks:
- isolated
deploy:
resources:
limits:
cpus: "${AGENT_CPUS:-2}"
memory: "${AGENT_MEMORY:-4g}"
profiles:
- run
networks:
isolated:
internal: true
internet:
driver: bridge
The agent service is under the run profile, so docker compose up only starts the proxy. Agents are started on demand with docker compose run.
Running Claude Code in the container
Start the proxy once:
docker compose up -d proxy
Run Claude Code against a project:
WORKSPACE=/path/to/project docker compose run --rm agent \
-p "Your prompt here" --dangerously-skip-permissions
Everything after agent is passed directly to the Claude CLI. You get access to every flag Claude supports without any wrapper getting in the way.
When you’re done:
docker compose down
Skipping permissions inside the sandbox
The --dangerously-skip-permissions flag tells Claude Code to execute all tool calls without asking for confirmation. Normally you wouldn’t want this — but inside an isolated container with filtered network access, it’s the right call. The container is the sandbox.
Running parallel Claude Code agents
Each docker compose run creates a separate container. Run as many as you need:
WORKSPACE=./project-a docker compose run --rm agent -p "Fix tests" --dangerously-skip-permissions &
WORKSPACE=./project-b docker compose run --rm agent -p "Add logging" --dangerously-skip-permissions &
wait
They share the proxy but are otherwise completely isolated from each other. This is how I run automated code review workflows and GitHub-issue-to-production pipelines across multiple repos at the same time.
API key instead of OAuth
If you prefer API key authentication over OAuth:
ANTHROPIC_API_KEY=sk-ant-xxx docker compose run --rm agent \
-p "Your prompt" --dangerously-skip-permissions
Resource limits
Default is 2 CPUs and 4 GB memory. Override with environment variables:
AGENT_CPUS=4 AGENT_MEMORY=8g WORKSPACE=./project docker compose run --rm agent \
-p "Heavy task" --dangerously-skip-permissions
Optional: wrapper script
If you don’t want to type docker compose -f /path/to/docker-compose.yml every time:
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
export WORKSPACE="${WORKSPACE:-$(pwd)}"
case "${1:-help}" in
up) docker compose -f "$SCRIPT_DIR/docker-compose.yml" up -d proxy ;;
down) docker compose -f "$SCRIPT_DIR/docker-compose.yml" down ;;
status) docker compose -f "$SCRIPT_DIR/docker-compose.yml" ps ;;
run) shift; docker compose -f "$SCRIPT_DIR/docker-compose.yml" run --rm agent "$@" ;;
*) echo "Usage: $0 {up|down|status|run [claude args...]}" ;;
esac
Or a shell alias in ~/.zshrc:
alias claude-docker='docker compose -f ~/path/to/docker-compose.yml'
Verifying the network isolation works
To confirm the Squid proxy is actually blocking traffic:
# This should fail (not on the allowlist)
docker compose run --rm agent curl -s https://example.com
# Connection refused
# This should succeed (on the allowlist)
docker compose run --rm agent curl -s -o /dev/null -w "%{http_code}" https://api.anthropic.com
# 404 (reachable, just no content at the root)
You can also tail the Squid access log while the agent runs — every outbound request shows up there, which is useful for auditing what Claude Code actually reached out to during a session.
Persisting Claude Pro / Max auth across container rebuilds
If you log in to Claude Code on the host with claude and pay for Pro / Max, the OAuth credential lives in ~/.claude/.credentials.json and ~/.claude.json. Re-doing that login every container rebuild is annoying — and on machines without an interactive browser it’s not even possible.
The fix is named volumes, not host-path mounts. Host-path mounts are read-only and tied to the host filesystem; named volumes survive docker compose build, docker compose down, and image rebuilds.
One-time setup: copy your existing host credentials into the named volume:
# Run once, after the first `docker compose up -d proxy`
docker run --rm \
-v claude-config:/dst \
-v "$HOME/.claude:/src:ro" \
alpine sh -c 'cp -r /src/. /dst/'
docker run --rm \
-v claude-json:/dst \
-v "$HOME/.claude.json:/src.json:ro" \
alpine sh -c 'cp /src.json /dst/host.json'
Then update entrypoint.sh to read from the volume mounts instead of host paths — the script in The Files above already does this if you mount the volumes at /host-claude and /host-claude-json. After that, every docker compose run --rm agent reuses the same OAuth session, even after docker compose build agent rebuilds the image.
Renewing the token: when your Pro/Max session expires (Anthropic refreshes OAuth periodically), re-run the one-time copy commands above. Or docker compose run --rm agent claude /login if you have an interactive browser inside the container.
This is the piece every other Claude-Code-in-Docker tutorial skips, and the reason most of them break the second time you rebuild.
Compose + Squid egress allowlist (with curl test output)
If you skipped the architecture section, here’s how the egress filter actually behaves end-to-end. Build the image, start the proxy, then test with curl from inside an ephemeral agent container:
$ docker compose build agent
$ docker compose up -d proxy
# Allowed domain — succeeds
$ docker compose run --rm --entrypoint sh agent -c \
'curl -s -o /dev/null -w "%{http_code}\n" https://api.anthropic.com'
404
# Allowed domain — succeeds (GitHub API root returns JSON)
$ docker compose run --rm --entrypoint sh agent -c \
'curl -s -o /dev/null -w "%{http_code}\n" https://api.github.com'
200
# Disallowed domain — Squid returns 403 to the client
$ docker compose run --rm --entrypoint sh agent -c \
'curl -s -o /dev/null -w "%{http_code}\n" https://example.com'
403
# Squid log shows the deny
$ docker compose logs proxy | tail -3
proxy-1 | 1747... TCP_DENIED/403 ... CONNECT example.com:443 ... HIER_NONE/-
Three tells that the allowlist is real:
example.comreturns 403 from Squid, not from the destination — the request never left your machine.- The Squid access log records
TCP_DENIED/403 ... HIER_NONE/-, the canonical “blocked at allowlist” entry. - Removing
HTTP_PROXYfrom the agent service and re-running theexample.comcurl returns a connect error (becauseisolated.internal: truemeans there’s no route to the internet without the proxy).
If your allowlist needs additional domains (Linear, Sentry, an internal package mirror), add them to squid.conf with acl allowed_sites dstdomain .your-domain.com, then docker compose restart proxy. No agent rebuild required.
Multi-project isolation via Compose profiles
Once you start running Claude Code agents on more than one project, you want each one to:
- See only that project’s workspace.
- Use its own environment variables (different
ANTHROPIC_API_KEYs, different proxy allowlists). - Run in parallel without colliding on container names.
Compose profiles + a small per-project override file make this clean. Define one base docker-compose.yml (the file above) and a per-project docker-compose.<project>.yml:
# docker-compose.web.yml — overrides for the "web" project
services:
agent:
profiles: [web]
container_name: claude-agent-web
volumes:
- /home/me/code/web:/workspace
environment:
ANTHROPIC_API_KEY: ${ANTHROPIC_API_KEY_WEB}
# docker-compose.infra.yml — overrides for the "infra" project, stricter allowlist
services:
proxy:
volumes:
- ./squid.infra.conf:/etc/squid/squid.conf:ro
agent:
profiles: [infra]
container_name: claude-agent-infra
volumes:
- /home/me/code/infra:/workspace
Then run them concurrently:
# Web project — with the default allowlist
docker compose -f docker-compose.yml -f docker-compose.web.yml \
--profile web run --rm agent -p "Add cookie consent" --dangerously-skip-permissions &
# Infra project — with a stricter allowlist (no GitHub, only Anthropic + internal registry)
docker compose -f docker-compose.yml -f docker-compose.infra.yml \
--profile infra run --rm agent -p "Audit Terraform" --dangerously-skip-permissions &
wait
Each agent gets its own filesystem, its own egress policy, and its own credential set. Two safety properties fall out for free:
- The
webagent cannot read files in/home/me/code/infrabecause the volume isn’t mounted. - The
infraagent cannot reachgithub.combecause its proxy uses the strictersquid.infra.conf.
That’s the multi-project isolation pattern. Useful when you want to run agentic refactors against several repos at once without one prompt accidentally polluting another.
When to use this vs. running Claude Code natively
| Situation | Docker + Squid isolation | Native claude |
|---|---|---|
| Interactive pair-programming on your own machine | Overkill | Better — no overhead |
| Batch / CI agent runs | Recommended | Risky |
| Running prompts from untrusted sources | Recommended | Dangerous |
| Parallel agents on isolated workspaces | Recommended | Works but no resource limits |
| Compliance / audit requirements | Recommended (Squid logs are great evidence) | Harder to justify |
For day-to-day coding with Claude Code I run it natively. For anything automated, untrusted, or multi-tenant, I run it in this Docker setup.
Related posts
- Automated code review with a Claude Code workflow
- From GitHub issue to production with Claude Code
- Agento: a Claude Code web UI AI-agent builder
FAQ
Is there an official Anthropic Docker image for Claude Code CLI?
No. Anthropic distributes Claude Code as an npm package (@anthropic-ai/claude-code). The Dockerfile above wrapping it in node:20-slim is currently the closest to an official image.
How do I install Claude Code CLI inside a Docker container?
Build the image with docker compose build agent. The Dockerfile uses npm install -g @anthropic-ai/claude-code on top of node:20-slim.
Does Claude Code CLI work with docker-compose?
Yes — see the docker-compose.yml above. The agent service is guarded by the run profile so docker compose up only starts the proxy; each invocation gets a fresh ephemeral container.
How do I isolate Claude Code’s network access?
Put the agent container on an internal: true Docker network with no bridge to the host, and route all HTTP(S) traffic through a Squid proxy with a dstdomain allowlist. Non-allowlisted domains fail closed.
Can I run multiple Claude Code agents in parallel?
Yes — each docker compose run --rm agent creates a separate container. They share the Squid proxy but are otherwise isolated.
Wrapping up
This setup gives you the best of both worlds. Claude Code gets full autonomy to read files, write code, run tests, and install packages. But it can only talk to the domains you approve. Docker handles the filesystem isolation, Squid handles the network filtering, and docker compose run handles spinning up ephemeral agents.
The whole thing is about 80 lines of configuration across four files. No custom tooling, no complex orchestration — just stock Docker, Docker Compose, and a battle-tested proxy.