← All posts
FEB 2026

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.

Docker container running Claude Code CLI with Squid proxy filtering network traffic
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-json volumes — 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 — extends node:20-slim, installs @anthropic-ai/claude-code via npm
  • entrypoint.sh — forwards your OAuth credentials into the container
  • squid.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:

  1. No direct internet from the agent container. The Docker network is marked internal: true, so the only way out is through the proxy.
  2. Allowlist, not blocklist. You name the domains the agent is allowed to reach (Anthropic API, GitHub, npm registry, etc.). Everything else fails closed.
  3. Transparent to the agent. Claude Code sees standard HTTP_PROXY / HTTPS_PROXY environment variables and respects them without any wrapper code.

Architecture

The setup has two pieces:

  1. A Squid proxy container that maintains the domain allowlist.
  2. 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-plugin package)
  • An Anthropic API key or an existing claude login on the host (the entrypoint forwards OAuth credentials from ~/.claude into 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:

  1. example.com returns 403 from Squid, not from the destination — the request never left your machine.
  2. The Squid access log records TCP_DENIED/403 ... HIER_NONE/-, the canonical “blocked at allowlist” entry.
  3. Removing HTTP_PROXY from the agent service and re-running the example.com curl returns a connect error (because isolated.internal: true means 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 web agent cannot read files in /home/me/code/infra because the volume isn’t mounted.
  • The infra agent cannot reach github.com because its proxy uses the stricter squid.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.

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.