diff --git a/.claude/rules/dockerfile.md b/.claude/rules/dockerfile.md index c8c92c7..b8875c1 100644 --- a/.claude/rules/dockerfile.md +++ b/.claude/rules/dockerfile.md @@ -41,36 +41,32 @@ Minimal images should include: `ca-certificates`, `git` - **Downloads**: Use download-then-extract (two commands) instead of `curl | tar` pipes — prevents masked failures under `/bin/sh` - **Parallel downloads**: For images with multiple binary tool downloads, use multi-stage builds — BuildKit runs independent stages concurrently. See `ralphex-fe/Dockerfile` for the pattern. -## Playwright on Debian +## Playwright / Chromium in Docker -Use the two-layer strategy (Chromium only for headless testing): +Use system Chromium instead of Playwright-managed browsers. This avoids version coupling +between `@playwright/mcp` (which uses alpha `playwright-core` builds) and cached browser binaries. +**Debian (Trixie):** ```dockerfile -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ - npx -y playwright@${PLAYWRIGHT_VERSION} install-deps chromium -USER app -RUN npx -y playwright@${PLAYWRIGHT_VERSION} install --only-shell chromium -USER root +# Install in the apt-get block: +chromium fonts-freefont-ttf + +# Set env vars: +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium ``` -## Alpine Playwright Support - -Do NOT use `playwright install` on Alpine — the bundled Chromium requires glibc. - -Instead: -1. Install system Chromium: `apk add --no-cache chromium ttf-freefont` -2. Set env vars (single `ENV` instruction to minimize layers): - ```dockerfile - ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ - PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser - ``` -3. In `playwright.config.ts`, wire up `executablePath`: - ```typescript - launchOptions: { - executablePath: process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH, - args: ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage'], - } - ``` -4. Projects install only `@playwright/test` — never run `playwright install` -5. `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` is a project convention; Playwright does NOT read it automatically +**Alpine:** +```dockerfile +RUN apk add --no-cache chromium ttf-freefont + +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium-browser +``` + +Note: Binary is `chromium` on Debian Trixie, `chromium-browser` on Alpine and older Debian. + +**For `@playwright/mcp`**, use `--executable-path` and `--no-sandbox` in the MCP config args. + +**`PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH`** is a project convention; Playwright does NOT read it automatically. +Use it in `playwright.config.ts` via `process.env.PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH` if needed. diff --git a/.github/workflows/build-ralphex-fe.yml b/.github/workflows/build-ralphex-fe.yml index 343aa0a..1a7785c 100644 --- a/.github/workflows/build-ralphex-fe.yml +++ b/.github/workflows/build-ralphex-fe.yml @@ -104,6 +104,7 @@ jobs: id app && test -f /init.sh && test -f /srv/init.sh && - test -d /home/app/.cache/ms-playwright && + chromium --no-sandbox --version && + test -x /usr/bin/chromium && echo 'All checks passed' " diff --git a/.github/workflows/update-and-build-ralphex-fe.yml b/.github/workflows/update-and-build-ralphex-fe.yml index 86dc0b5..f392213 100644 --- a/.github/workflows/update-and-build-ralphex-fe.yml +++ b/.github/workflows/update-and-build-ralphex-fe.yml @@ -155,10 +155,10 @@ jobs: version-tag: bun${{ needs.update-dockerfile.outputs.bun_version }}-hugo${{ needs.update-dockerfile.outputs.hugo_version }}-ralphex${{ needs.update-dockerfile.outputs.ralphex_version }} verify-command: 'bun --version && hugo version' extra-verify-script: | - chromium-browser --no-sandbox --version && \ - test "$PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" = "/usr/bin/chromium-browser" && \ + chromium --no-sandbox --version && \ + test "$PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" = "/usr/bin/chromium" && \ echo "OK: PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH is correct" || \ - (echo "FAIL: expected /usr/bin/chromium-browser, got $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && exit 1) && \ + (echo "FAIL: expected /usr/bin/chromium, got $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && exit 1) && \ test -x "$PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && \ echo "OK: binary is executable at $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" || \ (echo "FAIL: binary not executable at $PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH" && exit 1) && \ diff --git a/docs/plans/2026-04-01-fix-playwright-chromium-mismatch.md b/docs/plans/2026-04-01-fix-playwright-chromium-mismatch.md new file mode 100644 index 0000000..91eb4fc --- /dev/null +++ b/docs/plans/2026-04-01-fix-playwright-chromium-mismatch.md @@ -0,0 +1,95 @@ +# Plan: Fix Playwright Chromium version mismatch in ralphex-fe + +Replace Playwright-managed browser downloads with Debian's system Chromium package, eliminating the version coupling that causes 8+ minute browser re-downloads on every container start. + +## Context + +- The ralphex-fe Docker image caches Chromium at build time for `playwright@1.58.2` (revision 1208), but at runtime `@playwright/mcp@latest` depends on `playwright-core@1.60.0-alpha` which needs revision 1217 +- This is structurally unfixable with version pinning because `@playwright/mcp` releases use alpha Playwright builds that change nearly every release (43 of 63 releases had different versions) +- Files involved: + - `ralphex-fe/Dockerfile` (modify) + - `ralphex-fe/init-docker.sh` (modify) + - `.github/workflows/build-ralphex-fe.yml` (modify) + - `.github/workflows/update-and-build-ralphex-fe.yml` (modify) + - `.claude/rules/dockerfile.md` (modify) + +## Implementation Approach + +- Replace Playwright-managed browsers with Debian's system Chromium (`apt-get install chromium`) +- Use `@playwright/mcp`'s `--executable-path` flag to point at `/usr/bin/chromium`, bypassing Playwright's browser management entirely +- Set `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` and `PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium` env vars +- Expand init-docker.sh MCP patch from ARM64-only to all architectures (AMD64 also needs `chromium` not `chrome` in Docker) +- Add `--no-sandbox` for Docker environment (matches official MCP Dockerfile) + +--- + +## Task 1: Update Dockerfile — swap Playwright-managed browsers for system Chromium + +**Files:** +- Modify: `ralphex-fe/Dockerfile` + +**Changes:** +1. Remove `ARG PLAYWRIGHT_VERSION=1.58.2` (line 5) and `ARG PLAYWRIGHT_VERSION` re-declaration (line 80) +2. Add `chromium fonts-freefont-ttf` to the existing `apt-get install` block (lines 94-107) +3. Remove the entire Playwright install block (lines 153-162): `npx playwright install-deps` + `npx playwright install --only-shell` +4. Update ENV block (lines 180-183) — replace `PLAYWRIGHT_VERSION` with: + ``` + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium + ``` + +--- + +## Task 2: Update init-docker.sh — expand MCP patch to all architectures + +**Files:** +- Modify: `ralphex-fe/init-docker.sh` + +**Changes:** +- Remove the `uname -m` architecture check — apply patch on all architectures +- Add `--executable-path /usr/bin/chromium` to MCP args +- Add `--no-sandbox` for Docker environment +- Update comment to reflect new purpose + +--- + +## Task 3: Update CI workflows + +**Files:** +- Modify: `.github/workflows/build-ralphex-fe.yml` +- Modify: `.github/workflows/update-and-build-ralphex-fe.yml` + +**Changes for build-ralphex-fe.yml:** +- Update verify step: replace `test -d /home/app/.cache/ms-playwright` with `chromium --no-sandbox --version` and executable checks + +**Changes for update-and-build-ralphex-fe.yml:** +- Fix binary name: `chromium-browser` → `chromium` (correct for Debian Trixie) +- Fix env var path: `/usr/bin/chromium-browser` → `/usr/bin/chromium` + +--- + +## Task 4: Update documentation + +**Files:** +- Modify: `.claude/rules/dockerfile.md` + +**Changes:** +- Update the "Playwright on Debian" section to document the system Chromium approach + +--- + +## Verification + +1. `hadolint ralphex-fe/Dockerfile` +2. `shellcheck ralphex-fe/init-docker.sh` +3. YAML validation on both workflow files +4. Verify `chromium --no-sandbox --headless --version` works inside the built image +5. Confirm `--executable-path` is listed in `npx @playwright/mcp@latest --help` + +## Risk: CDP protocol compatibility + +System Chromium might differ from what `playwright-core` was tested against. Mitigations: +- Debian Trixie's Chromium tracks recent stable releases (close to Playwright alpha targets) +- MCP usage (browsing, clicking, extracting) doesn't need test-automation precision +- `--executable-path` is officially supported by `@playwright/mcp` +- Can pin `apt-get install chromium=VERSION` if needed diff --git a/ralphex-fe/Dockerfile b/ralphex-fe/Dockerfile index 91a8938..e232fa6 100644 --- a/ralphex-fe/Dockerfile +++ b/ralphex-fe/Dockerfile @@ -2,7 +2,6 @@ ARG BUN_VERSION=1.3.9 ARG DOCKER_VERSION=29.3.0 ARG GO_VERSION=1.24.4 ARG HUGO_VERSION=0.156.0 -ARG PLAYWRIGHT_VERSION=1.58.2 # ═════════════════════════════════════════════════════════════════════════════ # Parallel download stages — BuildKit runs these concurrently @@ -77,12 +76,13 @@ RUN set -eux; \ FROM node:24-trixie-slim ARG BUN_VERSION -ARG PLAYWRIGHT_VERSION # ── System packages ────────────────────────────────────────────────────────── # - ca-certificates: SSL/TLS for HTTPS connections +# - chromium: system browser for @playwright/mcp (bypasses Playwright's version-coupled downloads) # - curl: downloading tools and installers # - dumb-init: PID 1 init for proper signal handling (used by /init.sh entrypoint) +# - fonts-freefont-ttf: rendering fonts for headless Chromium screenshots # - fzf: fuzzy finder (interactive selection in Claude Code and shell) # - git: version control # - gosu: drop privileges to app user (Debian equivalent of Alpine's su-exec) @@ -95,8 +95,10 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ apt-get update && apt-get install -y --no-install-recommends \ ca-certificates \ + chromium \ curl \ dumb-init \ + fonts-freefont-ttf \ fzf \ git \ gosu \ @@ -118,9 +120,10 @@ ENV APP_USER=app \ DOCKER_GID=999 \ TIME_ZONE=America/Chicago -# Reserve GID 998 for ping to prevent conflicts (matches umputun/baseimage convention) -RUN groupadd -g 998 ping \ - && groupadd -g ${DOCKER_GID} docker \ +# Create groups for ping (GID 998) and docker (GID 999) matching umputun/baseimage convention. +# -f: exit successfully if GID already exists (chromium's systemd dep may claim these GIDs). +RUN groupadd -f -g 998 ping \ + && groupadd -f -g ${DOCKER_GID} docker \ && useradd --no-log-init -m -s /bin/sh -u ${APP_UID} ${APP_USER} \ && usermod -aG docker ${APP_USER} \ && mkdir -p /srv /workspace \ @@ -150,17 +153,6 @@ RUN chmod +x /init.sh /srv/init.sh WORKDIR /workspace -# ── Playwright (Chromium only — headless testing for Claude Code via ralphex) ─ -# Only Chromium headless shell is needed; Firefox/WebKit skipped. -# 1. System deps (install-deps) — heavy, needs root -# 2. Browser binary (install --only-shell chromium) — Chromium headless shell only -RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ - --mount=type=cache,target=/var/lib/apt/lists,sharing=locked \ - npx -y playwright@${PLAYWRIGHT_VERSION} install-deps chromium -USER app -RUN npx -y playwright@${PLAYWRIGHT_VERSION} install --only-shell chromium -USER root - # ── Claude Code CLI ────────────────────────────────────────────────────────── # npm install (not native installer) to avoid rate-limiting in parallel Docker builds. # See: claude-code/.devcontainer/Dockerfile for rationale. @@ -180,7 +172,8 @@ SHELL ["/bin/sh", "-c"] # ── Environment ────────────────────────────────────────────────────────────── ENV RALPHEX_DOCKER=1 \ USE_BUILTIN_RIPGREP=0 \ - PLAYWRIGHT_VERSION=${PLAYWRIGHT_VERSION} + PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 \ + PLAYWRIGHT_CHROMIUM_EXECUTABLE_PATH=/usr/bin/chromium # Ralphex web dashboard port EXPOSE 8080 diff --git a/ralphex-fe/init-docker.sh b/ralphex-fe/init-docker.sh index fe5a66f..b13b6c8 100755 --- a/ralphex-fe/init-docker.sh +++ b/ralphex-fe/init-docker.sh @@ -16,12 +16,12 @@ if [ -d /mnt/claude ]; then for d in commands skills hooks agents plugins; do [ -d "/mnt/claude/$d" ] && cp -rL "/mnt/claude/$d" "/home/app/.claude/" 2>/dev/null || true done - # ── Playwright MCP: use Chromium on ARM64 (#64) ──────────────────────── - # @playwright/mcp defaults to --browser chrome, which has no Linux ARM64 builds. - # Patch the plugin config to use chromium instead. No-op on AMD64. + # ── Playwright MCP: use system Chromium (#64, #67) ────────────────────── + # @playwright/mcp defaults to --browser chrome, which isn't installed in Docker. + # Point it at the system Chromium with --executable-path and --no-sandbox. PLAYWRIGHT_MCP_CONFIG="/home/app/.claude/plugins/marketplaces/claude-plugins-official/external_plugins/playwright/.mcp.json" - if [ "$(uname -m)" = "aarch64" ] && [ -f "$PLAYWRIGHT_MCP_CONFIG" ]; then - jq '.playwright.args = ["@playwright/mcp@latest", "--browser", "chromium"]' \ + if [ -f "$PLAYWRIGHT_MCP_CONFIG" ]; then + jq '.playwright.args = ["@playwright/mcp@latest", "--browser", "chromium", "--executable-path", "/usr/bin/chromium", "--no-sandbox"]' \ "$PLAYWRIGHT_MCP_CONFIG" > /tmp/playwright-mcp.json \ && mv /tmp/playwright-mcp.json "$PLAYWRIGHT_MCP_CONFIG" fi