Files
ddev-claude-cli/claude-code/browser/docker-compose.claude-browser.yaml

77 lines
3.7 KiB
YAML

#ddev-generated
# Opt-in headless browser for Claude Code: Playwright MCP with headless Chromium.
#
# This is the template. It does nothing where it is: "ddev claude-browser enable"
# copies it to .ddev/docker-compose.claude-browser.yaml and restarts the project, and
# install.sh registers it with Claude Code as the "playwright" MCP server
# (http://claude-browser:8931/mcp). While the copy keeps the #ddev-generated line,
# install.sh refreshes it from this template after add-on upgrades; remove that line
# from the copy to customise it.
#
# Pick another release with CLAUDE_BROWSER_IMAGE in .ddev/.env, e.g.
# CLAUDE_BROWSER_IMAGE=mcr.microsoft.com/playwright/mcp:v0.0.84
services:
claude-browser:
image: ${CLAUDE_BROWSER_IMAGE:-mcr.microsoft.com/playwright/mcp:v0.0.83}-${DDEV_SITENAME}-built
build:
# The upstream image runs as "node" (uid 1000). Turn that user into the host user
# (same name, uid and gid) so screenshots and other files the browser writes into
# the mounted project belong to you. Same approach as ddev-selenium-standalone-chrome.
dockerfile_inline: |
ARG CLAUDE_BROWSER_IMAGE="scratch"
FROM $${CLAUDE_BROWSER_IMAGE}
USER root
ARG username
ARG uid
ARG gid
RUN set -eu; \
old_uid="$$(id -u node)"; old_gid="$$(id -g node)"; \
for u in "$$(getent passwd "$${uid}" | cut -d: -f1)" "$$(getent passwd "$${username}" | cut -d: -f1)"; do \
if [ -n "$${u}" ] && [ "$${u}" != "node" ]; then userdel "$${u}"; fi; \
done; \
for g in "$$(getent group "$${gid}" | cut -d: -f1)" "$$(getent group "$${username}" | cut -d: -f1)"; do \
if [ -n "$${g}" ] && [ "$${g}" != "node" ]; then groupdel "$${g}" || true; fi; \
done; \
groupmod -o -g "$${gid}" node; \
if ! getent group "$${username}" >/dev/null; then groupmod -n "$${username}" node; fi; \
usermod -o -u "$${uid}" -g "$${gid}" node; \
if [ "$${username}" != "node" ]; then usermod -l "$${username}" node; fi; \
find / -xdev -uid "$${old_uid}" -exec chown -h "$${uid}" {} +; \
find / -xdev -gid "$${old_gid}" -exec chgrp -h "$${gid}" {} +
USER $${uid}:$${gid}
args:
CLAUDE_BROWSER_IMAGE: ${CLAUDE_BROWSER_IMAGE:-mcr.microsoft.com/playwright/mcp:v0.0.83}
username: ${DDEV_USER}
uid: ${DDEV_UID}
gid: ${DDEV_GID}
container_name: ddev-${DDEV_SITENAME}-claude-browser
user: "${DDEV_UID}:${DDEV_GID}"
restart: "no"
# Chromium needs more than Docker's 64 MB default /dev/shm for heavier pages.
shm_size: 1gb
# Appended to the image's entrypoint (node cli.js --headless --browser chromium --no-sandbox).
command:
- --port=8931
- --host=0.0.0.0
# The port is only exposed on the project's Docker network, never published to
# the host, so the Host-header check adds nothing here.
- --allowed-hosts=*
# DDEV's mkcert CA is not in this container's trust store.
- --ignore-https-errors
# Fresh in-memory profile per session, so parallel "ddev claude" sessions never
# fight over one profile directory.
- --isolated
# Same path as in the web container, so paths in tool results work for Claude as-is.
- --output-dir=/var/www/html/.ddev/claude-code/browser-output
- --file-paths=absolute
expose:
- 8931
volumes:
# The project at the web container's path: file uploads and saved screenshots
# use paths Claude can read directly.
- "../:/var/www/html"
x-ddev:
describe-info: |
Playwright MCP: http://claude-browser:8931/mcp
Toggle with: ddev claude-browser enable|disable