If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.
A DDEV add-on that adds a sandboxed sidecar
container for running Claude Code
(Anthropic’s AI coding CLI) with --dangerously-skip-permissions (YOLO
mode) safely contained behind an iptables + ipset + dnsmasq firewall.
claude sidecar service built from node:22-bookworm with:
chrome-devtools-mcp and @playwright/mcp MCP serversgh)github.com, api.github.comanthropic.com, claude.airegistry.npmjs.orgpackagist.org, repo.packagist.orgstorage.googleapis.comweb, db, etc.)ddev claude command that launches Claude Code in YOLO mode inside
the firewalled sidecar, as a non-root claude user (uid 1000).claude-config,
claude-history) — survives ddev restart and add-on rebuilds.Running AI coding agents autonomously is productive — until the agent
hallucinates a curl | sh against a compromised server, or an
adversarial prompt talks it into exfiltrating your .env file. This
add-on removes those risks by constraining the agent’s network reach at
the kernel level (iptables default-DROP) while still letting it fetch
dependencies from the usual package registries.
With the firewall active, --dangerously-skip-permissions becomes safe
enough for routine use: the worst the agent can do is corrupt your
working tree, and git reset --hard recovers from that.
ddev add-on get makraz/ddev-claude
ddev restart
Or from a local checkout (development):
ddev add-on get /path/to/ddev-claude
ddev restart
# Export your Anthropic API key (or use OAuth login on first run)
export ANTHROPIC_API_KEY=sk-ant-...
# Optional: GitHub auth for the gh CLI and the GitHub MCP
export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_...
# Start / restart the DDEV stack (first build takes ~5 minutes)
ddev restart
# Launch Claude Code in YOLO mode inside the sandbox
ddev claude
# Opt out of YOLO for a single run
CLAUDE_SAFE=1 ddev claude
# Pass extra flags through to the claude CLI
ddev claude --resume
ddev claude --help
Set EXTRA_ALLOWED_DOMAINS in your host shell (space-separated) before
ddev start / ddev restart:
export EXTRA_ALLOWED_DOMAINS="sentry.io api.stripe.com"
ddev restart
ddev claude
The firewall’s dnsmasq resolves these on demand and adds the resulting
IPs to the allow-list ipset. You can also persist them inside the
container at /etc/firewall/extra-domains.list (one per line).
After ddev claude starts, run a smoke test inside the session:
Please run:
curl -sS --max-time 5 https://api.github.com # should succeed
curl -sS --max-time 3 https://example.com || echo BLOCKED # should be BLOCKED
sudo iptables -L OUTPUT -n | head -1 # should say "policy DROP"
If example.com is reachable or the iptables policy is ACCEPT, the
firewall is not active — stop and investigate before trusting the agent
with autonomous work.
┌─ Host ───────────────────────────────────────────────────────────────┐
│ │
│ ddev claude ──► docker exec ──► ┌─ claude sidecar ──────────────┐ │
│ │ Claude Code CLI │ │
│ │ PHP 8.5 / Composer │ │
│ │ Playwright / Chromium │ │
│ │ │ │
│ │ iptables default DROP │ │
│ │ ipset allowed-ipv4 (dynamic) │ │
│ │ ipset allowed-net (ddev net) │ │
│ │ dnsmasq → ipset bridge │ │
│ │ │ │
│ │ mounts: /var/www/html (rw) │ │
│ │ /home/claude/.claude │ │
│ └──────────┬────────────────────┘ │
│ │ ddev default network │
│ ┌───────────────────────────────┼──────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─ web ──┐ ┌─ db ────┐ ┌─ other─┐ │
│ │ nginx │ │ mariadb │ │ ddev │ │
│ │ php-fpm│ │ │ │svcs... │ │
│ └────────┘ └─────────┘ └────────┘ │
└──────────────────────────────────────────────────────────────────────┘
init-firewall.sh (run as root via NOPASSWD sudoers entry) sets up:
allowed-ipv4 (hash:ip) and allowed-net (hash:net).dig resolves the default + extra
domains, populating allowed-ipv4.127.0.0.1, upstreams to 127.0.0.11
(Docker’s embedded DNS — keeps DDEV service names like web/db
resolvable), 1.1.1.1, 8.8.8.8. Each allow-listed domain is bound
via ipset=/<domain>/allowed-ipv4, so any future resolution
automatically extends the allow-list — handles CDN IP rotation.curl reachability checks for github + npm +
packagist (must succeed) and example.com (must fail).init-firewall.sh to
dual-stack the allow-list if needed..git and .env* are bind-mounted: the agent can read (and
potentially commit) anything in your project directory. Keep secrets
out of the working tree, or use CLAUDE_SAFE=1 for untrusted tasks.claude user inside the container is
uid 1000. If your host user uses a different uid, file ownership may
look strange on the host. Adjust via Dockerfile build args if needed.ddev add-on remove claude
ddev restart
The named volumes claude-config and claude-history are preserved so
that reinstalling the add-on later keeps your Claude Code auth. To
fully wipe them:
docker volume rm ddev-<project>_claude-config ddev-<project>_claude-history
MIT — see LICENSE.
If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.