Sandbox Your AI Coding Agent in a Locked-Down Docker Container A new guide from developer Mariana Souza details how to run Claude Code and Cursor's CLI agent inside a hardened Docker container with dropped Linux capabilities, CPU/memory/PID limits, and an iptables egress allowlist, plus a host-side approval gate. The setup, verified against Docker Engine 29.7, Claude Code 2.1.237, and the Cursor CLI 2026.08 build, restricts the agents to only reach necessary APIs like api.anthropic.com and api2.cursor.sh, blocking all other traffic. Sandbox Your AI Coding Agent in a Locked-Down Docker Container Run Claude Code and Cursor's CLI agent inside a hardened, egress-filtered container with a human approval gate. Mariana Souza https://sourcefeed.dev/u/mariana souza What you'll build / learn A hardened Docker container that runs Claude Code https://code.claude.com/docs/en/overview and the Cursor CLI https://cursor.com/docs/cli/overview in full-autonomy mode — with dropped Linux capabilities, CPU/memory/PID limits, an iptables egress allowlist so the agent can only reach the APIs it needs, and a host-side approval gate for anything privileged. php flowchart LR subgraph host Host W Your repo -- bind mount -- C A approve.sh -- "docker exec after y/N " -- C end subgraph C Locked-down container CC claude / agent as non-root user end C -- "allowlisted domains only" -- API api.anthropic.com, api2.cursor.sh, GitHub, npm C -. everything else .-x X blocked by iptables Prerequisites Verified August 2026 against: Docker Engine 29.7 any recent 27+ works , Claude Code 2.1.237, the Cursor CLI 2026.08 build, and the node:22-bookworm image Claude Code requires Node.js ≥ 22 . - Docker Engine or Docker Desktop on Linux or macOS Windows: use WSL2 . - A Claude subscription or Anthropic Console account. Run claude setup-token on your host and copy the long-lived token it prints, or grab an API key from the Console. - Optional, for Cursor: an API key from cursor.com → Dashboard → API Keys. 1. Vendor the egress firewall script Anthropic ships a reference firewall for exactly this purpose. It sets iptables default policies to DROP , resolves an allowlist of domains into an ipset , and self-tests on startup. Download it into a fresh project directory: mkdir agent-sandbox && cd agent-sandbox curl -fsSL -o init-firewall.sh \ https://raw.githubusercontent.com/anthropics/claude-code/main/.devcontainer/init-firewall.sh Out of the box it allows the Anthropic API, GitHub, and npm. The ipset holds resolved IPs, not wildcards, so if you'll run Cursor's agent, add its backend domains documented here https://cursor.com/docs/enterprise/network-configuration to the for domain in loop in the script: "api2.cursor.sh" \ "api3.cursor.sh" \ "repo42.cursor.sh" \ "authenticator.cursor.sh" \ "downloads.cursor.com" \ 2. Write the entrypoint The firewall needs root and NET ADMIN , but the agent must never have either. So the container starts as root, raises the firewall, then drops to an unprivileged user with gosu — no sudo installed, so there's no way back up. Save as entrypoint.sh : bash /bin/bash set -euo pipefail /usr/local/bin/init-firewall.sh exec gosu node "$@" 3. Write the Dockerfile FROM node:22-bookworm aggregate, ipset, dnsutils, jq: required by init-firewall.sh RUN apt-get update && apt-get install -y --no-install-recommends \ iptables ipset dnsutils jq curl ca-certificates git gosu aggregate \ && rm -rf /var/lib/apt/lists/ RUN npm install -g @anthropic-ai/claude-code COPY init-firewall.sh entrypoint.sh /usr/local/bin/ RUN chmod +x /usr/local/bin/init-firewall.sh /usr/local/bin/entrypoint.sh \ && mkdir -p /home/node/.claude /workspace \ && chown -R node:node /home/node /workspace Cursor CLI installs to ~/.local/bin for the invoking user USER node RUN curl https://cursor.com/install -fsS | bash ENV PATH="/home/node/.local/bin:${PATH}" USER root ENTRYPOINT "/usr/local/bin/entrypoint.sh" CMD "bash" Build it: docker build -t agent-sandbox . 4. Run it locked down Export credentials on the host never bake them into the image , then start the container from your project's directory. Save as run-sandbox.sh next to the Dockerfile: bash /usr/bin/env bash set -euo pipefail export CLAUDE CODE OAUTH TOKEN=... from claude setup-token export CURSOR API KEY=... optional docker run --rm -it --name agent-sandbox \ --cap-drop=ALL \ --cap-add=NET ADMIN --cap-add=NET RAW \ --cap-add=SETUID --cap-add=SETGID \ --security-opt no-new-privileges:true \ --memory=4g --memory-swap=4g --cpus=2 --pids-limit=512 \ -v "$PWD":/workspace \ -v claude-sandbox-config:/home/node/.claude \ -e CLAUDE CONFIG DIR=/home/node/.claude \ -e CLAUDE CODE OAUTH TOKEN \ -e CURSOR API KEY \ -w /workspace \ agent-sandbox What each layer buys you: --cap-drop=ALL strips every capability except the four the entrypoint needs NET ADMIN / NET RAW for iptables, SETUID / SETGID for the gosu drop ; no-new-privileges blocks re-escalation via setuid binaries; --memory-swap equal to --memory disables swap so a runaway build gets OOM-killed instead of thrashing your host; --pids-limit caps fork bombs. The only host surface is the bind-mounted repo. Don't mount ~/.ssh or cloud credential files — anything visible in the container is visible to the agent. Inside, run the agents at full autonomy — the container is the guardrail: claude --dangerously-skip-permissions or Cursor, headless: agent -p --force "add input validation to server.js" 5. Add the approval-gated exec bridge The agent runs as node with no sudo, so it can't install system packages or touch iptables. When it needs something privileged — say apt-get install ripgrep — it has to ask, and you approve from the host. Save as approve.sh on the host and chmod +x it: bash /usr/bin/env bash set -euo pipefail echo "Sandbox requests: $ " read -rp "Run as root inside the container? y/N " reply if "$reply" == "y" ; then docker exec --privileged -u root -it agent-sandbox "$@" else echo "Denied." fi Usage, while the sandbox is running: ./approve.sh bash -c 'apt-get update && apt-get install -y ripgrep' docker exec --privileged grants full capabilities to that one approved command only; the agent's own shell stays stripped. Verify it works Startup should end with the firewall's self-test before your prompt appears: Firewall verification passed - unable to reach https://example.com as expected Firewall verification passed - able to reach https://api.github.com as expected node@2f8c1a9b3d47:/workspace$ Confirm the lockdown from inside the container: curl -m 5 -sI https://example.com || echo BLOCKED prints BLOCKED after 5s curl -s https://api.github.com/zen prints a zen aphorism claude --version 2.1.237 Claude Code agent --version prints the Cursor CLI build claude -p "reply with exactly: sandbox ok" sandbox ok And the resource caps from the host: docker inspect -f '{{.HostConfig.Memory}} {{.HostConfig.PidsLimit}}' agent-sandbox 4294967296 512 Troubleshooting at startup — the container is missing network capabilities. You dropped iptables v1.8.9 nf tables : Could not fetch rule set generation id: Permission denied you must be root ALL without adding back NET ADMIN and NET RAW easy to do when porting the docker run flags to Compose: use cap add .— you swapped in a base image without the /usr/local/bin/init-firewall.sh: line 30: aggregate: command not found aggregate package, which the script uses to merge GitHub's CIDR ranges. Add aggregate to the apt-get install line and rebuild.— you're running --dangerously-skip-permissions cannot be used with root/sudo privileges for security reasons claude as root, probably from a docker exec -u root shell. Claude Code refuses bypass mode as root by design; run it from the main shell, where the entrypoint already dropped to node . macOS — your project directory is outside Docker Desktop's allowed shares. Add it under Settings → Resources → File Sharing. Error response from daemon: Mounts denied: The path ... is not shared from the host Next steps - Anthropic's reference devcontainer https://code.claude.com/docs/en/devcontainer wraps this same Dockerfile-plus-firewall pattern in the Dev Containers spec, so VS Code, Cursor, and JetBrains can attach an editor to the sandbox. - Read Claude Code's security model https://code.claude.com/docs/en/security and sandbox environments https://code.claude.com/docs/en/sandbox-environments comparison — the built-in Bash sandbox may be enough when you don't need full container isolation. - Pin versions for reproducibility: install @anthropic-ai/claude-code@2.1.237 explicitly and set DISABLE AUTOUPDATER=1 in containerEnv so the agent can't update itself mid-session. - Harden further with a read-only root filesystem --read-only plus tmpfs mounts or run the whole thing rootless with Rootless Docker https://docs.docker.com/engine/security/rootless/ . Sources & further reading - Development containers - Claude Code Docs https://code.claude.com/docs/en/devcontainer — code.claude.com - init-firewall.sh reference egress firewall https://github.com/anthropics/claude-code/blob/main/.devcontainer/init-firewall.sh — github.com - Claude Code reference devcontainer configuration https://github.com/anthropics/claude-code/blob/main/.devcontainer/devcontainer.json — github.com - Using Headless CLI - Cursor Docs https://cursor.com/docs/cli/headless — cursor.com - Network Configuration - Cursor Docs https://cursor.com/docs/enterprise/network-configuration — cursor.com - Docker Engine version 29 release notes https://docs.docker.com/engine/release-notes/29/ — docs.docker.com Mariana Souza https://sourcefeed.dev/u/mariana souza · Senior Editor Mariana covers the fast-moving world of machine learning and generative AI, with a particular focus on how these technologies are reshaping development workflows. When she isn't stress-testing the latest foundation models, she's usually at a local hackathon. Discussion 0 No comments yet Be the first to weigh in.