← All posts
FEB 2026

Running Claude Code CLI in Docker with Network Isolation

How to run Claude Code CLI inside a Docker container with a Squid proxy that only allows traffic to specific domains. Full setup with docker-compose, OAuth credential forwarding, and parallel agent support.

Docker container running Claude Code CLI with Squid proxy filtering network traffic
On this page

Claude Code CLI can run bash commands, install packages, and make network requests. That’s powerful, but it also means a single bad prompt could reach places you don’t want it to. I wanted to run Claude Code in an environment where it has full autonomy over the filesystem but can only talk to a handful of approved domains.

Here’s how I set it up with Docker and a Squid proxy.

What We’re Building

The setup has two pieces:

  1. A Squid proxy that maintains a domain allowlist. Only traffic to approved domains gets through.
  2. A Claude Code container that sits on an internal Docker network with no direct internet access. 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. But if it tries to reach example.com or any domain not on the list, the proxy blocks it.

The Files

You need four files. Here’s each one.

Dockerfile

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. This makes volume mount permissions work without extra configuration.

entrypoint.sh

Claude Code reads OAuth credentials from ~/.claude and ~/.claude.json. We mount the host’s credentials as 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

This is the allowlist. Edit it 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

One thing to watch out for: Squid doesn’t allow overlapping entries. If you add .anthropic.com (with the leading dot, which covers all subdomains), don’t also add api.anthropic.com. Squid will refuse to start.

docker-compose.yml

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.

Usage

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

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 makes sense. The container is the sandbox.

Parallel 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.

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 4GB 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, a small wrapper script helps:

#!/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 add a shell alias to your ~/.zshrc:

alias claude-docker='docker compose -f ~/path/to/docker-compose.yml'

Then from anywhere:

claude-docker run --rm agent -p "Your prompt" --dangerously-skip-permissions

Verifying the Isolation

To confirm the 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)

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. The Squid proxy handles the filtering, Docker handles the isolation, 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.