Skip to content

feat(docker): add a browserless-compatible Chrome driver and images - #645

Open
route wants to merge 3 commits into
mainfrom
feat/docker
Open

route wants to merge 3 commits into
mainfrom
feat/docker

Conversation

@route

@route route commented Oct 12, 2026

Copy link
Copy Markdown
Member

What

A self-hosted replacement for browserless, aimed at Ferrum and Cuprite users: a small Go server (docker/) and two images, ghcr.io/rubycdp/chrome and ghcr.io/rubycdp/chromium, for amd64 and arm64.

docker run --rm -p 3000:3000 ghcr.io/rubycdp/chromium
Ferrum::Browser.new(url: "http://localhost:3000")

Why

Most remote-Chrome issues reported here come from browserless's own logic, not from CDP. Its Puppeteer resets the viewport (#592, #492), it writes a raw HTTP 408 onto a live socket (#538), it reports 0.0.0.0 as the WebSocket host (#501), and it leaves Chrome processes and zombies behind (#505, #364). docker/DESIGN.md lists all of them.

How

  • One Chrome per connection. Each Chrome runs in its own process group with a fresh profile, on a random port read from its stderr. When the connection closes, the group gets TERM, then KILL, and the profile is deleted.
  • Bytes pass through unchanged. The WebSocket goes through httputil.ReverseProxy. Nothing else talks to the client's targets.
  • /json/version is answered on the fly. It starts the session's Chrome, returns its real metadata, and rewrites the ws URL to a reachable host while keeping the query. A Chrome nobody connects to is stopped after 30s. Connections to / claim a Chrome already started with the same query, so ws_url: clients don't start two.
  • Routes match browserless v2: /, /chrome, /chromium, /devtools/browser/<id>, with or without a trailing slash, plus /health.
  • Images.
    • debian:trixie-slim with dumb-init as PID 1.
    • Runs under any uid.
    • Open fonts stand in for Microsoft ones.
    • Google Chrome (.deb) or Debian Chromium.
    • No Node.
  • CI. It builds each browser and architecture on native runners (no QEMU) and smoke-tests every image. On main it publishes latest, the major version and the full version as multi-arch tags.

Testing

Run locally on arm64 with Ferrum, against both images and against the bare binary with a local Chrome:

  • the url: and ws_url: flows, including /chrome/;
  • 3 parallel sessions;
  • no Chrome or zombie processes left after quit;
  • unclaimed browsers stopped after 30s;
  • docker stop stopping everything in about 0.15s.

The amd64 images and this workflow haven't run yet. This PR is their first run.

Not in this PR

  • CONCURRENT/QUEUED/TIMEOUT, the launch= query parameter, and the DevTools viewer proxy.
  • An image HEALTHCHECK and Go tests.
  • The Ferrum side: applying window_size remotely, and a fix so Context#add_target never makes a service worker the default page.

route added 3 commits October 12, 2026 21:12
A small Go server that looks like browserless v2 from the outside but runs no Puppeteer: each WebSocket
connection gets its own Chrome, traffic is proxied unchanged, and Chrome's process group is killed when the
connection ends. /json/version starts the session's Chrome and returns its real metadata with a reachable
ws URL; unclaimed browsers are stopped after 30s. DESIGN.md lists the browserless pitfalls it avoids.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant