{"slug": "sandbox-your-ai-coding-agent-in-a-locked-down-docker-container", "title": "Sandbox Your AI Coding Agent in a Locked-Down Docker Container", "summary": "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.", "body_md": "# Sandbox Your AI Coding Agent in a Locked-Down Docker Container\n\nRun Claude Code and Cursor's CLI agent inside a hardened, egress-filtered container with a human approval gate.\n\n[Mariana Souza](https://sourcefeed.dev/u/mariana_souza)\n\n## What you'll build / learn\n\nA 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.\n\n``` php\nflowchart LR\n    subgraph host [Host]\n        W[Your repo] -- bind mount --> C\n        A[approve.sh] -- \"docker exec (after y/N)\" --> C\n    end\n    subgraph C [Locked-down container]\n        CC[claude / agent as non-root user]\n    end\n    C -- \"allowlisted domains only\" --> API[api.anthropic.com, api2.cursor.sh, GitHub, npm]\n    C -. everything else .-x X[blocked by iptables]\n```\n\n## Prerequisites\n\nVerified 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`\n\nimage (Claude Code requires Node.js ≥ 22).\n\n- Docker Engine or Docker Desktop on Linux or macOS (Windows: use WSL2).\n- A Claude subscription or Anthropic Console account. Run\n`claude setup-token`\n\non 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.\n\n## 1. Vendor the egress firewall script\n\nAnthropic ships a reference firewall for exactly this purpose. It sets `iptables`\n\ndefault policies to `DROP`\n\n, resolves an allowlist of domains into an `ipset`\n\n, and self-tests on startup. Download it into a fresh project directory:\n\n```\nmkdir agent-sandbox && cd agent-sandbox\ncurl -fsSL -o init-firewall.sh \\\n  https://raw.githubusercontent.com/anthropics/claude-code/main/.devcontainer/init-firewall.sh\n```\n\nOut 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`\n\nloop in the script:\n\n```\n    \"api2.cursor.sh\" \\\n    \"api3.cursor.sh\" \\\n    \"repo42.cursor.sh\" \\\n    \"authenticator.cursor.sh\" \\\n    \"downloads.cursor.com\" \\\n```\n\n## 2. Write the entrypoint\n\nThe firewall needs root and `NET_ADMIN`\n\n, but the agent must never have either. So the container starts as root, raises the firewall, then drops to an unprivileged user with `gosu`\n\n— no `sudo`\n\ninstalled, so there's no way back up. Save as `entrypoint.sh`\n\n:\n\n``` bash\n#!/bin/bash\nset -euo pipefail\n/usr/local/bin/init-firewall.sh\nexec gosu node \"$@\"\n```\n\n## 3. Write the Dockerfile\n\n```\nFROM node:22-bookworm\n\n# aggregate, ipset, dnsutils, jq: required by init-firewall.sh\nRUN apt-get update && apt-get install -y --no-install-recommends \\\n      iptables ipset dnsutils jq curl ca-certificates git gosu aggregate \\\n    && rm -rf /var/lib/apt/lists/*\n\nRUN npm install -g @anthropic-ai/claude-code\n\nCOPY init-firewall.sh entrypoint.sh /usr/local/bin/\nRUN chmod +x /usr/local/bin/init-firewall.sh /usr/local/bin/entrypoint.sh \\\n    && mkdir -p /home/node/.claude /workspace \\\n    && chown -R node:node /home/node /workspace\n\n# Cursor CLI installs to ~/.local/bin for the invoking user\nUSER node\nRUN curl https://cursor.com/install -fsS | bash\nENV PATH=\"/home/node/.local/bin:${PATH}\"\n\nUSER root\nENTRYPOINT [\"/usr/local/bin/entrypoint.sh\"]\nCMD [\"bash\"]\n```\n\nBuild it:\n\n```\ndocker build -t agent-sandbox .\n```\n\n## 4. Run it locked down\n\nExport credentials on the host (never bake them into the image), then start the container from your project's directory. Save as `run-sandbox.sh`\n\nnext to the Dockerfile:\n\n``` bash\n#!/usr/bin/env bash\nset -euo pipefail\n# export CLAUDE_CODE_OAUTH_TOKEN=...   (from `claude setup-token`)\n# export CURSOR_API_KEY=...            (optional)\ndocker run --rm -it --name agent-sandbox \\\n  --cap-drop=ALL \\\n  --cap-add=NET_ADMIN --cap-add=NET_RAW \\\n  --cap-add=SETUID --cap-add=SETGID \\\n  --security-opt no-new-privileges:true \\\n  --memory=4g --memory-swap=4g --cpus=2 --pids-limit=512 \\\n  -v \"$PWD\":/workspace \\\n  -v claude-sandbox-config:/home/node/.claude \\\n  -e CLAUDE_CONFIG_DIR=/home/node/.claude \\\n  -e CLAUDE_CODE_OAUTH_TOKEN \\\n  -e CURSOR_API_KEY \\\n  -w /workspace \\\n  agent-sandbox\n```\n\nWhat each layer buys you: `--cap-drop=ALL`\n\nstrips every capability except the four the entrypoint needs (`NET_ADMIN`\n\n/`NET_RAW`\n\nfor iptables, `SETUID`\n\n/`SETGID`\n\nfor the gosu drop); `no-new-privileges`\n\nblocks re-escalation via setuid binaries; `--memory-swap`\n\nequal to `--memory`\n\ndisables swap so a runaway build gets OOM-killed instead of thrashing your host; `--pids-limit`\n\ncaps fork bombs. The only host surface is the bind-mounted repo. Don't mount `~/.ssh`\n\nor cloud credential files — anything visible in the container is visible to the agent.\n\nInside, run the agents at full autonomy — the container is the guardrail:\n\n```\nclaude --dangerously-skip-permissions\n# or Cursor, headless:\nagent -p --force \"add input validation to server.js\"\n```\n\n## 5. Add the approval-gated exec bridge\n\nThe agent runs as `node`\n\nwith no sudo, so it can't install system packages or touch iptables. When it needs something privileged — say `apt-get install ripgrep`\n\n— it has to ask, and you approve from the host. Save as `approve.sh`\n\non the host and `chmod +x`\n\nit:\n\n``` bash\n#!/usr/bin/env bash\nset -euo pipefail\necho \"Sandbox requests: $*\"\nread -rp \"Run as root inside the container? [y/N] \" reply\nif [[ \"$reply\" == \"y\" ]]; then\n  docker exec --privileged -u root -it agent-sandbox \"$@\"\nelse\n  echo \"Denied.\"\nfi\n```\n\nUsage, while the sandbox is running:\n\n```\n./approve.sh bash -c 'apt-get update && apt-get install -y ripgrep'\n```\n\n`docker exec --privileged`\n\ngrants full capabilities to that one approved command only; the agent's own shell stays stripped.\n\n## Verify it works\n\nStartup should end with the firewall's self-test before your prompt appears:\n\n```\nFirewall verification passed - unable to reach https://example.com as expected\nFirewall verification passed - able to reach https://api.github.com as expected\nnode@2f8c1a9b3d47:/workspace$\n```\n\nConfirm the lockdown from inside the container:\n\n```\ncurl -m 5 -sI https://example.com || echo BLOCKED   # prints BLOCKED after 5s\ncurl -s https://api.github.com/zen                  # prints a zen aphorism\nclaude --version                                    # 2.1.237 (Claude Code)\nagent --version                                     # prints the Cursor CLI build\nclaude -p \"reply with exactly: sandbox ok\"          # sandbox ok\n```\n\nAnd the resource caps from the host:\n\n```\ndocker inspect -f '{{.HostConfig.Memory}} {{.HostConfig.PidsLimit}}' agent-sandbox\n# 4294967296 512\n```\n\n## Troubleshooting\n\nat 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)`\n\n`ALL`\n\nwithout adding back`NET_ADMIN`\n\nand`NET_RAW`\n\n(easy to do when porting the`docker run`\n\nflags to Compose: use`cap_add`\n\n).— you swapped in a base image without the`/usr/local/bin/init-firewall.sh: line 30: aggregate: command not found`\n\n`aggregate`\n\npackage, which the script uses to merge GitHub's CIDR ranges. Add`aggregate`\n\nto the`apt-get install`\n\nline and rebuild.— you're running`--dangerously-skip-permissions cannot be used with root/sudo privileges for security reasons`\n\n`claude`\n\nas root, probably from a`docker exec -u root`\n\nshell. Claude Code refuses bypass mode as root by design; run it from the main shell, where the entrypoint already dropped to`node`\n\n.(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`\n\n## Next steps\n\n- Anthropic's\n[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\n[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\n`@anthropic-ai/claude-code@2.1.237`\n\nexplicitly and set`DISABLE_AUTOUPDATER=1`\n\nin`containerEnv`\n\nso the agent can't update itself mid-session. - Harden further with a read-only root filesystem (\n`--read-only`\n\nplus tmpfs mounts) or run the whole thing rootless with[Rootless Docker](https://docs.docker.com/engine/security/rootless/).\n\n## Sources & further reading\n\n-\n[Development containers - Claude Code Docs](https://code.claude.com/docs/en/devcontainer)— code.claude.com -\n[init-firewall.sh reference egress firewall](https://github.com/anthropics/claude-code/blob/main/.devcontainer/init-firewall.sh)— github.com -\n[Claude Code reference devcontainer configuration](https://github.com/anthropics/claude-code/blob/main/.devcontainer/devcontainer.json)— github.com -\n[Using Headless CLI - Cursor Docs](https://cursor.com/docs/cli/headless)— cursor.com -\n[Network Configuration - Cursor Docs](https://cursor.com/docs/enterprise/network-configuration)— cursor.com -\n[Docker Engine version 29 release notes](https://docs.docker.com/engine/release-notes/29/)— docs.docker.com\n\n[Mariana Souza](https://sourcefeed.dev/u/mariana_souza)· Senior Editor\n\nMariana 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.\n\n## Discussion 0\n\nNo comments yet\n\nBe the first to weigh in.", "url": "https://wpnews.pro/news/sandbox-your-ai-coding-agent-in-a-locked-down-docker-container", "canonical_source": "https://sourcefeed.dev/a/sandbox-your-ai-coding-agent-in-a-locked-down-docker-container", "published_at": "2026-08-20 11:42:02+00:00", "updated_at": "2026-08-20 12:14:16.902684+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "ai-safety", "ai-infrastructure"], "entities": ["Mariana Souza", "Claude Code", "Cursor CLI", "Docker", "Anthropic", "Cursor"], "alternates": {"html": "https://wpnews.pro/news/sandbox-your-ai-coding-agent-in-a-locked-down-docker-container", "markdown": "https://wpnews.pro/news/sandbox-your-ai-coding-agent-in-a-locked-down-docker-container.md", "text": "https://wpnews.pro/news/sandbox-your-ai-coding-agent-in-a-locked-down-docker-container.txt", "jsonld": "https://wpnews.pro/news/sandbox-your-ai-coding-agent-in-a-locked-down-docker-container.jsonld"}}