For AI agents: a documentation index is available at /llms.txt
Skip to main content

Open Source Docker Deployment

The open-source Browserless image runs headless browsers inside Docker with built-in support for Puppeteer, Playwright, and REST APIs. It's free, self-hosted, and available on GitHub Container Registry.

What you get:

  • Browser images for Chromium, Chrome, Firefox, WebKit, and Edge
  • Native Puppeteer and Playwright connectivity over WebSocket
  • REST APIs for screenshots, PDFs, scraping, and more
  • Session management, health checks, and a built-in debugger UI
Looking for Enterprise?

The open-source image covers core browser automation. If you need BrowserQL, stealth/CAPTCHA solving, session recording, live debugging, webhooks, or OpenTelemetry, see Enterprise Docker.

Available images

All images are published to ghcr.io/browserless/ and support both linux/amd64 and linux/arm64 architectures.

ImageBrowsers IncludedPull Command
chromiumChromiumdocker pull ghcr.io/browserless/chromium
chromeChromedocker pull ghcr.io/browserless/chrome
firefoxFirefoxdocker pull ghcr.io/browserless/firefox
webkitWebKitdocker pull ghcr.io/browserless/webkit
edgeMicrosoft Edgedocker pull ghcr.io/browserless/edge
multiAll of the abovedocker pull ghcr.io/browserless/multi
ARM limitations

Chrome and Edge are only available on linux/amd64. The multi image on ARM includes Chromium, Firefox, and WebKit only.

Quick start

  1. Run the Container

    Start a Browserless container with a token and concurrency limit:

    docker run \
    --rm \
    -p 3000:3000 \
    -e "CONCURRENT=10" \
    -e "TOKEN=6R0W53R135510" \
    ghcr.io/browserless/chromium
    warning

    Browserless always requires a token. If you don't set TOKEN, Browserless generates a random token and prints it to stdout on startup.

  2. Connect Your Application

    Point your automation library at the running container:

    const puppeteer = require("puppeteer-core");

    const browser = await puppeteer.connect({
    browserWSEndpoint: "ws://localhost:3000?token=6R0W53R135510",
    });
    const page = await browser.newPage();
    await page.goto("https://example.com");
    const screenshot = await page.screenshot();
    await browser.close();
  3. Verify

    Confirm the instance is running with a REST API call:

    curl "http://localhost:3000/chromium/content?token=6R0W53R135510&url=https://example.com"

    You should get back the rendered HTML of example.com:

    <!doctype html>
    <html>
    <head>
    <title>Example Domain</title>
    ...

    Or open http://localhost:3000 in your browser to see the built-in docs and debugger UI.

Docker compose

For repeatable deployments, use a docker-compose.yml:

services:
browserless:
image: ghcr.io/browserless/chromium
ports:
- "3000:3000"
environment:
- TOKEN=6R0W53R135510
- CONCURRENT=10
- QUEUED=10
- TIMEOUT=30000
shm_size: "2g"
restart: unless-stopped
docker compose up -d
Shared memory

Always set shm_size: "2g" (or --shm-size=2g on the CLI). Docker defaults to 64 MB of shared memory, which causes Chrome to crash under load.

Endpoints

The open-source image exposes REST APIs, WebSocket endpoints, and management routes.

REST APIs

Each browser has its own path prefix. For Chromium:

EndpointMethodDescription
/chromium/contentPOSTReturns rendered HTML content of a page
/chromium/pdfPOSTGenerates a PDF from a page
/chromium/screenshotPOSTCaptures a viewport screenshot as PNG or JPEG
/chromium/scrapePOSTScrapes structured data from a page
/chromium/downloadPOSTDownloads a file triggered by page interaction
/chromium/functionPOSTRuns a custom function with a browser context
/chromium/performancePOSTReturns performance metrics for a page

Replace /chromium with /chrome, /firefox, /webkit, or /edge when using those browser images.

All REST endpoints accept a ?token= query parameter for authentication.

WebSocket endpoints

Connect automation libraries over WebSocket:

EndpointDescription
/chromium/playwrightPlaywright WebSocket connection
/?token= (root)Puppeteer CDP WebSocket connection
/chromiumCDP connection for Chromium

Management endpoints

EndpointMethodDescription
/activeGETLiveliness probe (returns 204)
/configGETCurrent server configuration
/metricsGETSession and performance metrics
/metrics/totalGETAggregate metrics
/pressureGETCurrent CPU, memory, and queue pressure
/sessionsGETActive browser sessions

Configuration reference

Configure the container with environment variables. Pass them with -e on the CLI or in the environment block of your Compose file.

Core settings

VariableDescriptionDefault
TOKENAPI token for authenticating requests. If unset, Browserless generates a random token on startup.(random)
CONCURRENTMaximum concurrent browser sessions. Additional requests queue.10
QUEUEDMaximum queued requests. Once full, new requests get a 429 response.10
TIMEOUTSession timeout in milliseconds. Set to -1 to disable (make sure your code closes browsers).30000
PORTInternal HTTP port. Map externally with Docker's -p flag.3000
HOSTAddress to bind to. Set to 0.0.0.0 in Docker so other containers on the same network can connect.localhost

Storage

VariableDescriptionDefault
DATA_DIRUser data directory for cookies, cache, and local storage. Mount a volume for persistence.OS temp dir
DOWNLOAD_DIRDirectory for file downloads. Mount a volume to access downloaded files.OS temp dir
METRICS_JSON_PATHPath to persist metrics across restarts.OS temp dir

Health checks

VariableDescriptionDefault
HEALTHEnable pre-request health checks. Returns 503 when CPU or memory usage exceeds the threshold.false
MAX_CPU_PERCENTCPU usage threshold (requires HEALTH=true).99
MAX_MEMORY_PERCENTMemory usage threshold (requires HEALTH=true).99

Networking & security

VariableDescriptionDefault
CORSEnable CORS headers.false
CORS_ALLOW_ORIGINAllowed CORS origins.*
CORS_ALLOW_METHODSAllowed HTTP methods for CORS.OPTIONS, POST, GET
CORS_MAX_AGECORS preflight cache max age in seconds.2592000
ALLOW_GETAllow GET requests with URL-encoded JSON body.false
ALLOW_FILE_PROTOCOLAllow file:// URLs.false
EXTERNALPublic-facing URL for link generation (when behind a reverse proxy).(none)

Debugging

VariableDescriptionDefault
DEBUGDebug log namespaces (debug module). * for all, -* for none.browserless*,-**:verbose
TZContainer timezone.UTC
ENABLE_DEBUGGERShow the built-in debugger UI at the root URL.true

Webhook alerts

VariableTrigger
QUEUE_ALERT_URLRequests start queuing
REJECT_ALERT_URLRequests rejected (429)
TIMEOUT_ALERT_URLSessions timeout
ERROR_ALERT_URLUnhandled errors
FAILED_HEALTH_URLHealth check failures

Common recipes

Set the timezone

docker run --rm -p 3000:3000 \
-e "TOKEN=6R0W53R135510" \
-e "TZ=America/New_York" \
ghcr.io/browserless/chromium

Tune concurrency and queueing

Allow 20 concurrent sessions with a queue of 30, and a 5-minute session timeout:

docker run --rm -p 3000:3000 \
-e "TOKEN=6R0W53R135510" \
-e "CONCURRENT=20" \
-e "QUEUED=30" \
-e "TIMEOUT=300000" \
ghcr.io/browserless/chromium

Use a proxy

Pass proxy settings per-session through launch arguments. Browserless doesn't bundle a proxy server, so you'll need to bring your own.

const browser = await puppeteer.connect({
browserWSEndpoint:
"ws://localhost:3000?token=6R0W53R135510&--proxy-server=http://proxy.example.com:8080",
});

Persist user data (sessions, cookies, cache)

Mount a volume to DATA_DIR so browser data survives container restarts:

docker run --rm -p 3000:3000 \
-e "TOKEN=6R0W53R135510" \
-e "DATA_DIR=/data" \
-v /path/on/host:/data \
ghcr.io/browserless/chromium

This forces all sessions to share the same user data directory. To use different profiles per session, override the user data directory in your automation code via launch arguments instead.

Enable health checks

Reject new sessions when the container is under heavy load:

docker run --rm -p 3000:3000 \
-e "TOKEN=6R0W53R135510" \
-e "HEALTH=true" \
-e "MAX_CPU_PERCENT=80" \
-e "MAX_MEMORY_PERCENT=80" \
ghcr.io/browserless/chromium

Run behind a reverse proxy

When Browserless sits behind NGINX or another proxy, set EXTERNAL so generated URLs (like those in the /sessions response) point to the correct address:

docker run --rm -p 3000:3000 \
-e "TOKEN=6R0W53R135510" \
-e "EXTERNAL=https://browserless.yourcompany.com" \
ghcr.io/browserless/chromium

For full load-balancing setups, see NGINX Load Balancing.

Upgrading

Images on ghcr.io/browserless/ use a latest tag and versioned tags (e.g., ghcr.io/browserless/chromium:2.26.1).

To upgrade:

  1. Pull the new image:
    docker pull ghcr.io/browserless/chromium:latest
  2. Stop and remove the running container.
  3. Start a new container with the same configuration.

For Compose deployments:

docker compose pull
docker compose up -d
Pin versions in production

Use a specific version tag instead of latest so upgrades are intentional:

image: ghcr.io/browserless/chromium:2.26.1

Check the GitHub releases for changelogs and breaking changes before upgrading.

Open Source vs. Cloud vs. Enterprise

FeatureOpen SourceCloudEnterprise Docker
Puppeteer / PlaywrightYesYesYes
REST APIs (screenshot, PDF, scrape)YesYesYes
Session managementBasicManagedAdvanced (priority, persistence)
BrowserQLNoYesYes
Stealth / CAPTCHA solvingNoYesYes
Session recordingNoYesYes
Live debuggerNoYesYes
WebhooksAlerts onlyN/AFull lifecycle events
OpenTelemetryNoN/AYes
Token rolesNoN/AYes
Managed proxiesNoYesNo (bring your own)
SupportCommunity (GitHub Issues)EmailPriority engineering support
PricingFreeUsage-basedLicense-based

Ready to upgrade? See the Enterprise Deployment Guide or contact sales.

Troubleshooting & FAQ

Chrome crashes with "out of memory"

Docker defaults shared memory (/dev/shm) to 64 MB. Chrome needs more.

Fix: Add --shm-size=2g to your docker run command, or shm_size: "2g" in Compose.

"Connection refused" when connecting from another container

Browserless binds to localhost by default, which isn't reachable from other containers on the same Docker network.

Fix: Set HOST=0.0.0.0:

docker run --rm -p 3000:3000 \
-e "TOKEN=6R0W53R135510" \
-e "HOST=0.0.0.0" \
ghcr.io/browserless/chromium
How do I find the auto-generated token?

If you don't set TOKEN, Browserless generates one on startup and prints it to stdout. Check your container logs:

docker logs <container_id>
Getting 429 responses

Your request queue is full. Either increase CONCURRENT and QUEUED, or slow down your request rate. Check /pressure to see current load.

Sessions hang or don't close

Set a reasonable TIMEOUT (default is 30 seconds). If you set TIMEOUT=-1, make sure your code calls browser.close(). Otherwise sessions stay open indefinitely and consume resources.

Fonts or characters render incorrectly

The Docker images include common fonts for Latin, CJK, Arabic, Thai, and Indic scripts. If you need additional fonts, extend the image with your own Dockerfile.

How do I use Firefox or WebKit instead of Chromium?

Use the corresponding image and WebSocket path:

# Firefox
docker run --rm -p 3000:3000 -e "TOKEN=6R0W53R135510" ghcr.io/browserless/firefox

# WebKit
docker run --rm -p 3000:3000 -e "TOKEN=6R0W53R135510" ghcr.io/browserless/webkit

Connect with Playwright (Firefox and WebKit don't support CDP, so Puppeteer won't work):

const browser = await playwright.firefox.connect("ws://localhost:3000/firefox/playwright?token=6R0W53R135510");
Can I run multiple browsers in one container?

Yes, use the multi image. It includes all supported browsers and exposes each on its own path (/chromium, /chrome, /firefox, /webkit, /edge).

Where do I report bugs or request features?

Open an issue on GitHub.