# Rodney + Docker Headless Chrome When Chrome/Chromium can't run directly in the host environment (permission issues, snap confinement, missing capabilities), use a Docker container as a minimal Chrome host for Rodney. ## Why Docker? Rodney needs a Chrome DevTools Protocol (CDP) endpoint. Chrome requires: - **`CAP_SYS_ADMIN`** — for sandbox namespace creation (even with `--no-sandbox`, Chrome internally checks for this) - **Read/execute access to Chrome binary** — often blocked when the binary is root-owned and the process has no `CAP_DAC_OVERRIDE` - **Writable temp directory** — for user data/profile In restricted environments (e.g. pi agent with zero capabilities, snap confinement), these are hard to grant minimally on the host. Docker isolates the Chrome process with just the one capability it needs. ## Build ```bash # Copy the Chromium binary that `rodney start` downloads: cp -r ~/.cache/rod/browser/chromium-*/ .pi/skills/demo/chrome/ # Build the image: docker build -t demo-chrome .pi/skills/demo/ ``` The Dockerfile is at `.pi/skills/demo/Dockerfile`. It uses: - `debian:bookworm-slim` as base (minimal) - Pre-downloaded Chromium from rod's cache (`~/.cache/rod/browser/chromium-*/`) - Only the shared libraries Chrome actually needs (no snap, no desktop deps) ## Run ```bash docker run -d --name demo-chrome \ --cap-add=SYS_ADMIN --cap-drop=ALL \ --security-opt=no-new-privileges:false \ --network=host \ demo-chrome ``` ### Flag breakdown | Flag | Why | |------|-----| | `--cap-add=SYS_ADMIN --cap-drop=ALL` | Drop all capabilities, add only what Chrome needs. Blast radius: one capability in one container. | | `--security-opt=no-new-privileges:false` | Allows the setuid `chrome-sandbox` to escalate. Required for Chrome's sandbox helper. | | `--network=host` | **Critical** — Chrome binds to `127.0.0.1` inside the container. Docker port mapping (`-p`) doesn't work reliably with Chrome's localhost binding. `--network=host` makes Chrome bind directly to the host's network namespace. | ## Connect ```bash rodney connect localhost:9222 ``` Chrome exposes the CDP endpoint on port 9222. Rodney connects via WebSocket. ## Verify ```bash # Check Chrome is responding: curl -s http://localhost:9222/json/version # Take a screenshot: rodney open http://localhost:8056/microprints rodney waitidle rodney screenshot demos/screenshots/microprints.png ``` ## Stop ```bash docker stop demo-chrome && docker rm demo-chrome ``` ## Troubleshooting ### Chrome not responding on port 9222 - Check logs: `docker logs demo-chrome` - Look for `"DevTools listening on ws://..."` — if present, Chrome is running - If you see `libXfixes.so.3: cannot open shared object`, the Dockerfile needs `libxfixes3` in the apt install ### Connection reset by peer - Chrome is binding to `127.0.0.1` inside the container. Docker port mapping (`-p 9222:9222`) doesn't forward correctly. - **Fix**: Use `--network=host` instead of `-p`. ### Permission denied on `/opt/google/chrome/chrome` - The process has zero capabilities (`CapEff: 0000000000000000`). Can't execute root-owned binaries. - **Fix**: Docker container with `--cap-add=SYS_ADMIN` bypasses this. ### dconf errors - Chrome tries to write to `/run/user/$UID/dconf` which may be read-only or permission-restricted. - **Fix**: Docker container has its own writable filesystem. ### dbus errors (harmless) - `Failed to connect to the bus` — Chrome tries to connect to system dbus which doesn't exist in the container. - **Ignore** — these are non-fatal warnings. Chrome works fine without dbus. ### GLib-GIO-CRITICAL errors (harmless) - `g_settings_schema_source_lookup: assertion 'source != NULL' failed` — Chrome tries to read GTK settings which don't exist. - **Ignore** — non-fatal warnings in headless mode.