Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 24 additions & 28 deletions .claude/rules/dockerfile.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
3 changes: 2 additions & 1 deletion .github/workflows/build-ralphex-fe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
"
6 changes: 3 additions & 3 deletions .github/workflows/update-and-build-ralphex-fe.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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) && \
Expand Down
95 changes: 95 additions & 0 deletions docs/plans/2026-04-01-fix-playwright-chromium-mismatch.md
Original file line number Diff line number Diff line change
@@ -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
27 changes: 10 additions & 17 deletions ralphex-fe/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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 \
Expand All @@ -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 \
Expand Down Expand Up @@ -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.
Expand All @@ -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
Expand Down
10 changes: 5 additions & 5 deletions ralphex-fe/init-docker.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down