Raised: $0
0% of monthly goal Help us cross the finish line!
Goal: $12,000
Raised: $0 Goal: $12,000
0% of monthly goal Help us cross the finish line!
Sponsor DDEV

If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.

DDEV Claude

add-on registry tests last commit release

Overview

This add-on integrates Claude Code, Anthropic’s AI coding CLI, into your DDEV project as a sandboxed sidecar container. Claude runs with --dangerously-skip-permissions (YOLO mode) safely contained behind an iptables + ipset + dnsmasq firewall, so an agent off the rails cannot exfiltrate your .env, your SSH keys, or anything else outside the project tree.

The default sidecar is minimum viable: Claude Code + firewall on a pre-built debian:bookworm-slim base image. Project-specific tools (PHP, gh, Playwright, etc.) are opt-in via a small extras catalog or a Dockerfile escape hatch.

Installation

ddev add-on get makraz/ddev-claude
ddev restart

To pin to a specific version (see Releases for what’s available):

ddev add-on get makraz/[email protected]
ddev restart

After ddev add-on get, commit the changes to your project’s .ddev/ directory.

Usage

Command Description
ddev claude Interactive Claude Code session (YOLO mode, default).
ddev claude safe Same, but without --dangerously-skip-permissions.
ddev claude shell Drop into bash inside the sidecar (firewall active).
ddev claude exec <cmd> Run one command in the sidecar non-interactively.
ddev claude rebuild Regenerate .ddev/claude/Dockerfile after editing .ddev/claude.yaml.
ddev claude help Print the help text.
ddev claude <args> Anything else passes through to the claude CLI (e.g. --resume).

The sidecar is reachable as the claude service on the DDEV default network. Auth tokens and settings persist at .ddev/.claude/ (gitignored by default).

Environment variables

Set on your host shell before ddev start / ddev restart. The sidecar’s docker-compose.claude.yaml forwards them into the container.

Variable Default Description
ANTHROPIC_API_KEY unset API key. Optional — OAuth flow runs on first launch if unset.
GITHUB_PERSONAL_ACCESS_TOKEN unset Forwarded to the sidecar so the agent can git push to private repos. Also exported as GH_TOKEN for gh and GitHub MCP fragments added via the escape hatch.
EXTRA_ALLOWED_DOMAINS unset Space-separated extra outbound domains, allow-listed at runtime. Prefer .ddev/claude.yaml’s extra_allowed_domains: for project-level settings.
PLAYWRIGHT_BASE_URL https://web Pre-set inside the container so Playwright/MCP fragments added via the escape hatch hit the DDEV web service by default. Override on the host shell if needed.
CLAUDE_SAFE 0 Read by the ddev claude host command. Set to 1 to opt out of YOLO mode for a single invocation (equivalent to ddev claude safe).

Configuration

Per-project configuration lives in .ddev/claude.yaml (user-editable):

# Available extras: php
extras:
  - php

# Additional outbound domains the runtime firewall should allow.
extra_allowed_domains:
  - sentry.io
  - api.stripe.com

If the file is absent or both lists are empty, the sidecar is built minimum-viable.

Available extras

Extra What it installs Adds to firewall allow-list
php PHP 8.5 CLI + Composer + common extensions (bcmath, curl, gd, intl, mbstring, mysql, soap, xml, xsl, zip) via Ondřej Surý’s apt repo. packagist.org, repo.packagist.org

Edit .ddev/claude.yaml, run ddev claude rebuild (or ddev restart), and the image is regenerated.

Advanced Customization

For tooling not in the catalog, drop fragments into .ddev/claude.local/:

GitHub CLI

.ddev/claude.local/Dockerfile.fragment:

USER root
RUN apt-get update && apt-get install -y --no-install-recommends \
      gnupg lsb-release \
 && mkdir -p -m 755 /etc/apt/keyrings \
 && curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
      | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
 && chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
 && echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
      > /etc/apt/sources.list.d/github-cli.list \
 && apt-get update && apt-get install -y --no-install-recommends gh \
 && rm -rf /var/lib/apt/lists/*

(github.com is already in the default allow-list — no extra-domains.list entry needed.)

Node.js + npm (for npm-based MCPs)

.ddev/claude.local/Dockerfile.fragment:

USER root
RUN curl -fsSL https://deb.nodesource.com/setup_22.x | bash - \
 && apt-get install -y --no-install-recommends nodejs \
 && rm -rf /var/lib/apt/lists/*

.ddev/claude.local/extra-domains.list:

deb.nodesource.com
registry.npmjs.org

Playwright + Chromium

.ddev/claude.local/Dockerfile.fragment:

USER root
ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
RUN mkdir -p "$PLAYWRIGHT_BROWSERS_PATH" \
 && npx --yes playwright install --with-deps chromium \
 && npm install -g @playwright/mcp chrome-devtools-mcp \
 && chmod -R a+rX "$PLAYWRIGHT_BROWSERS_PATH"

.ddev/claude.local/extra-domains.list:

storage.googleapis.com

(Requires the Node.js fragment first.)

Components

A claude sidecar built from a pre-built multi-arch base image (ghcr.io/makraz/ddev-claude-base:<version>, published from this repo) containing:

An outbound firewall (default-DROP policy) that allows only:

The image tag is pinned to the addon version 1:1. Installing ddev-claude@<tag> always pulls ddev-claude-base:<tag> — no floating :latest. The exact Claude Code build baked into a given image is recorded in the OCI label io.makraz.ddev-claude.claude-version and visible via docker inspect.

Verifying the sandbox

After ddev claude starts, run a smoke test inside the session:

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.

How the firewall works

init-firewall.sh (run as root via the NOPASSWD sudoers entry) sets up:

  1. ipsetsallowed-ipv4 (hash:ip) and allowed-net (hash:net).
  2. Initial DNS resolutiondig resolves the default + extra domains, populating allowed-ipv4.
  3. dnsmasq — listens on 127.0.0.1, upstreams to 127.0.0.11 (Docker’s embedded DNS, so DDEV service names like web/db resolve), 1.1.1.1, 8.8.8.8. Each allow-listed domain is bound via ipset=/<domain>/allowed-ipv4, so future resolutions automatically extend the allow-list — handles CDN IP rotation.
  4. iptables — default policy DROP on INPUT/OUTPUT/FORWARD. ACCEPT only loopback, established/related, DNS (port 53), the two ipsets, the host gateway, and inbound 80/443.
  5. IPv6 — dropped entirely.
  6. Smoke tests — reachability checks for github (must succeed) and example.com (must fail).

Developing the addon

The published image (ghcr.io/makraz/ddev-claude-base:<version>) is built from image/Dockerfile by .github/workflows/publish-image.yml on every git tag push. To iterate on image/Dockerfile without publishing:

# Build locally with the tag the addon expects:
TAG=$(grep -m1 'FROM ghcr.io/.*/ddev-claude-base:' .ddev/claude/Dockerfile.base | sed 's|.*:||')
docker build -t "ghcr.io/makraz/ddev-claude-base:${TAG}" image/

# ddev restart picks up the local image (Docker's default `missing`
# pull policy uses local images when present).
ddev restart

To cut a release:

git tag v0.3.0-beta.1            # or v0.3.0 for stable
git push origin v0.3.0-beta.1
# publish-image.yml builds + pushes the image to GHCR (~5 min, multi-arch).

gh release create v0.3.0-beta.1 --prerelease --notes-file release-notes.md

Known limitations

Removing

ddev add-on remove claude
ddev restart

Add-on-managed files are removed. User-managed files are preserved (delete manually if desired):

rm -rf .ddev/claude.yaml .ddev/.claude/ .ddev/claude.local/

Credits

Contributed and maintained by @makraz

Firewall sandboxing approach inspired by Anthropic’s official Claude Code devcontainer reference.

If you find this add-on useful, please star it on GitHub — stars show appreciation and help maintainers know their work matters.