Self-hosted service
browserless/browserless avatar
browserless/browserless

browserless/browserless: self-hosted headless Chrome in Docker

Deploy headless browsers in Docker. Run on our cloud or bring your own. Free for non-commercial uses.

13,749 stars1,043 forksTypeScriptNOASSERTION

At a glance

What is it?
Browserless packages Chromium, Firefox and WebKit behind a WebSocket endpoint you connect to with standard Puppeteer or Playwright. The Docker path is short; the licence is the part most teams underestimate.
Who is it for?
Adopt browserless if you already write Puppeteer or Playwright scripts and want them to run somewhere other than the machine running your app, and if your use is non-commercial or you are prepared to buy a commercial licence. Skip it if you need a permissively licensed component to redistribute inside your own product, or if a single `chromium --headless` subprocess in your existing worker already does the job.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem browserless solves: browser processes that outlive your request handler

Driving a headless browser from application code is awkward for reasons that have nothing to do with the automation API. A Chromium process is heavy, it leaks file descriptors and shared memory over time, and it dies in ways your Node process usually survives. If you spawn one per request you pay startup cost on every request; if you keep a pool, you own the pool's health checks, timeouts and crash recovery.

Browserless takes that responsibility out of your application. The README describes it as a way to "Deploy headless browsers in Docker. Run on our cloud or bring your own." The service runs the browser, exposes a WebSocket endpoint, and your code connects to it with the standard Puppeteer or Playwright client rather than launching a local browser. The README lists the operational concerns it claims to handle: parallelism and queueing with configurable concurrency limits, configurable session timers and health checks, and error tolerance, described as "If Chrome crashes, Browserless won't."

The intended audience is teams that already have Puppeteer or Playwright scripts and want them to run on a host that is not the one serving HTTP traffic. That includes screenshot and PDF services, scraping pipelines, and end-to-end test runs pointed at a shared browser host. Because the client libraries are unforked, an existing script usually needs its launch call replaced with a connect call and nothing else.

How the WebSocket endpoint and per-browser routes are laid out

The architecture is a long-running Node service (the package is TypeScript, `"type": "module"`, and ships a `browserless` binary in package.json) that supervises browser processes and speaks the Chrome DevTools Protocol over a WebSocket. Puppeteer connects to the bare origin, `ws://localhost:3000`. Playwright does not use that same endpoint shape; the README's Playwright example connects to a browser-specific path, `ws://localhost:3000/firefox/playwright`, and notes that you need `ghcr.io/browserless/firefox` or `ghcr.io/browserless/multi` for Firefox and WebKit support.

That path structure is the detail worth internalising. The image you pull decides which browsers exist, and the route you connect to selects one. Pulling `ghcr.io/browserless/chromium` and then pointing Playwright at a Firefox route will not work, because the browser is not in the container. The `multi` image is the one that carries more than one engine.

Around the browser processes sit the features the README lists: a queue with concurrency limits, session timers, health checks, and a debug viewer for watching running sessions. The queue is the part with real consequences. Concurrency limits mean requests can wait rather than fail, so a burst of traffic shows up as latency in your client, not as an error, unless you have configured a timeout that fires first. The README does not state the default concurrency or timeout values, so treat those as things to read from the running service's configuration rather than assume.

There is a second, HTTP-shaped surface. The README points at `http://localhost:3000/docs` for documentation, and the repository's build scripts include `build:openapi`, which generates an OpenAPI description. That implies REST endpoints exist alongside the WebSocket ones, but the README excerpt does not enumerate them, so check `/docs` on a running container rather than guessing at paths.

Running the Chromium image and connecting Puppeteer in five minutes

The README's quick start is three steps. Start the container, open the docs, connect a script. The image is on GitHub Container Registry, not Docker Hub, so the pull command names ghcr.io explicitly.

bash
docker run -p 3000:3000 ghcr.io/browserless/chromium

That maps container port 3000 to the host. Once it is up, the README says to visit `http://localhost:3000/docs` in a browser, and states that the service is live at `ws://localhost:3000`. If the page does not load, the container is not the problem you should debug first; check that nothing else on the host already holds port 3000.

The Puppeteer client is `puppeteer-core`, which does not download a browser of its own. That is the point: the browser lives in the container.

js
import puppeteer from 'puppeteer-core';

const browser = await puppeteer.connect({
  browserWSEndpoint: 'ws://localhost:3000',
});

const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();

According to the README, this prints `Example Domain`. Note the `close()` call: it closes the browser context, not the remote browser process, which the service keeps alive for the next connection.

For Playwright the connection target changes, and so does the image requirement. The README's example uses `playwright-core` and the Firefox route.

js
import pw from 'playwright-core';

const browser = await pw.firefox.connect(
  'ws://localhost:3000/firefox/playwright',
);

const page = await browser.newPage();
await page.goto('https://example.com');
console.log(await page.title());
await browser.close();

Run that against `ghcr.io/browserless/chromium` and it will fail. The README's own note says to use `ghcr.io/browserless/firefox` or `ghcr.io/browserless/multi` for Firefox and WebKit support.

The SSPL licence is the constraint that decides most adoptions

package.json declares `"license": "SSPL"`, and the repository root carries both LICENSE and NOTICE.txt. The GitHub API reports the licence as NOASSERTION, which is what you get when the declared licence is not one GitHub's classifier recognises; the authoritative text is in the repository, not in the API field.

The SSPL is the Server Side Public License. It is a copyleft licence written to cover software offered to third parties as a service, and its obligations attach to operating the software for others rather than to distributing binaries. That is a materially different trigger from the GPL family, and it is the single fact most likely to change an adoption decision. The README frames the free tier as "Free for non-commercial uses", which is a narrower grant than "open source" in the everyday sense, and the repository's own topics and README use the open-source framing for a project whose licence is not OSI-approved.

This is not legal advice and the summary above is not a substitute for the text. If your plan is to run browserless behind a paid product, to embed it in something you ship to customers, or to offer it as part of a hosted service, read LICENSE and NOTICE.txt and get your own answer. The repository also carries MIGRATION-2.0.md, which matters if you are on a 1.x deployment, and the README's feature list separates "General Features" from "Premium Features" that belong to the cloud and Enterprise offerings: BrowserQL, hybrid automations, persistent sessions, session replay, Chrome extensions support, and advanced captcha and stealth routes. None of those appear in the self-hosted general list, so do not plan around them.

Where browserless is the wrong tool

The most common misjudgement is reaching for it when you do not need a browser at all. If the page you are fetching is server-rendered HTML, an HTTP client is faster, cheaper and has no crash surface. Browserless adds a container, a queue and a WebSocket hop on top of a browser process you did not need.

The second is treating it as a stealth layer. The README places anti-detection features, fingerprint randomisation and captcha solving under Premium Features, not under the general self-hosted list. A self-hosted container runs a normal headless browser with a normal headless fingerprint. Sites that block headless browsers will still block this one, and the README does not claim otherwise.

Third, ARM64. The README lists ARM64 support as a general feature, including Apple Silicon, but immediately qualifies it: "some browsers (Edge, Chrome) have limited ARM64 compatibility". If your deployment target is ARM and your scripts depend on Chrome specifically, verify the browser you need actually runs there before you build a pipeline on it.

Finally, resource accounting. The README does not state memory or CPU requirements per concurrent session, and the concurrency default is not given. A container that serves one session comfortably can behave very differently at ten. Size the host from measurement on your own workload, not from the README, because the README does not give you a number.

Self-hosted container versus a managed browser API

The real alternative is not a different library; it is not running the browser yourself. Playwright and Puppeteer both launch browsers locally by default, and both can point at a remote endpoint. The difference between browserless and a managed browser service is who owns the operational surface.

Self-hosting puts the container, its restarts, its image updates, its port, its concurrency limits and its memory ceiling on you. In exchange you keep the traffic inside your network, you control the browser version, and you pay for a host rather than per session. The README's own framing is exactly this split: "Run on our cloud or bring your own."

A managed service moves the queue, the scaling and the browser patching to the vendor, and typically adds the features browserless reserves for its paid tiers, including persistent session state and the stealth routes. The trade is that your page content leaves your network and your cost scales with session volume rather than with a fixed host.

If you are choosing between them, the deciding question is not cost per session but whether you can operate a stateful browser pool. A team that already runs containers with health checks and rolling updates will find the self-hosted path unremarkable. A team that does not will spend its first month on container restarts and memory limits rather than on automation.

Upgrades, release cadence and what to watch

The last push to the default branch was on 2026-09-21, and the most recent releases listed are v2.56.7 on 2026-09-10, v2.56.6 on 2026-09-09 and v2.56.5 on 2026-09-09. Those are patch-level version bumps landing in quick succession, which is the shape of a project that patches often and expects operators to keep up.

The upgrade cost sits mostly in the browser, not in browserless. Each image tag pins a browser build, so moving tags moves the Chromium, Firefox or WebKit version underneath your scripts, and that is what breaks selectors and timing assumptions. Pinning a digest rather than a floating tag gives you a reproducible browser and makes the upgrade an explicit act. The repository carries MIGRATION-2.0.md for the 1.x to 2.x transition, which is the one migration the project has documented in the root listing; there is no equivalent file for 2.x minor bumps, so read CHANGELOG.md for those.

The repository also carries a `.nvmrc` and a `package.json` with `"type": "module"`, so anyone building from source rather than pulling the image should match the pinned Node version. The build is not a single command: `npm run build` chains `build:ts`, `install:adblock`, `build:schemas`, `build:devtools` and `build:openapi`, and `npm run install:browsers` invokes the Playwright CLI to install chromium, firefox, webkit and msedge. Building from source is a heavier path than pulling `ghcr.io/browserless/chromium`, and for most adopters the image is the right entry point.

Editorial conclusion

Adopt browserless if you already write Puppeteer or Playwright scripts and want them to run somewhere other than the machine running your app, and if your use is non-commercial or you are prepared to buy a commercial licence. Skip it if you need a permissively licensed component to redistribute inside your own product, or if a single `chromium --headless` subprocess in your existing worker already does the job. Before you commit, read LICENSE and NOTICE.txt in the repository root, confirm which of the ghcr.io/browserless images matches your target browser, and check whether your usage falls under the free non-commercial grant the README describes.

Frequently asked questions

Is browserless free?

The README describes it as free for non-commercial uses, and package.json declares the SSPL licence. Commercial use is a separate question you should answer from LICENSE and NOTICE.txt in the repository root rather than from the README's summary.

Can browserless be self-hosted?

Yes. The README's quick start runs the container locally with docker run -p 3000:3000 ghcr.io/browserless/chromium and connects Puppeteer to ws://localhost:3000. The README frames the choice as running on the project's cloud or bringing your own.

What does browserless actually do?

It runs headless browsers in a container and exposes a WebSocket endpoint that standard Puppeteer and Playwright clients connect to instead of launching a local browser. The README lists parallelism and queueing, configurable timeouts and health checks, and tolerance of Chrome crashes among its general features.

Is browserless open source?

The source is public on GitHub and the repository is not archived, but package.json declares the SSPL licence and GitHub reports the licence as NOASSERTION. The SSPL is not an OSI-approved licence, so the open-source label needs that qualification.

Is browserless safe to run?

The README does not make security claims, and nothing in the repository documentation describes sandboxing, authentication or network isolation for the container. Treat the exposed WebSocket endpoint as you would any unauthenticated service and decide from the project's own documentation.

Does browserless work with Playwright as well as Puppeteer?

Yes, but the connection target differs. The README's Playwright example connects to ws://localhost:3000/firefox/playwright and notes that Firefox and WebKit require the ghcr.io/browserless/firefox or ghcr.io/browserless/multi image.

Official sources

  1. browserless/browserless on GitHub
  2. Issues
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/browserless-browserless.svg)](https://hysenlabs.com/projects/browserless-browserless)