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.

Quickstart

  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

    If you don't set TOKEN, Browserless does not generate one for you. Every endpoint is left unauthenticated, including /function, which runs arbitrary Puppeteer code from the request body. Set TOKEN for any deployment reachable beyond localhost.

  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 not set, all endpoints are unauthenticated, including /function.(none)
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. The image already sets this to 0.0.0.0, which accepts connections on every interface inside the container. Reaching it from another container still needs a shared Docker network and a published port. The underlying library defaults to localhost only when run outside Docker.0.0.0.0

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
Core REST APIs (/screenshot, /pdf, /content, /scrape, /function, /download, /performance)YesYesYes
Advanced REST APIs (/unblock, /smart-scrape, /search, /map, /crawl, /agent/run)NoYesNo
Session managementBasicManagedAdvanced (priority, persistence)
BrowserQLNoYesYes
Stealth / CAPTCHA solvingNoYesYes
blockAds parameterYesYesYes
blockConsentModals parameterNoYesYes
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.

FAQ & Troubleshooting

Is Smart Scrape (/smart-scrape) available in the open-source image?

No. /smart-scrape is a Cloud-only endpoint, along with /unblock, /search, /map, /crawl, and /agent/run. The closest open-source equivalents are /scrape for structured extraction and /content for rendered HTML.

Can I use stealth or /unblock in the open-source image?

No. Stealth routes and CAPTCHA solving require Cloud or an Enterprise license, and /unblock is Cloud-only. The open-source image runs standard browsers; you can still pass your own launch flags and proxies.

Do blockAds and blockConsentModals work in the open-source image?

blockAds works: the image ships a built-in ad blocker you can enable per request or connect call. blockConsentModals is not in the open-source image; it's available on Cloud and Enterprise.

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

The image already binds to 0.0.0.0 by default, so this is usually something else: a firewall, the two containers not sharing a Docker network, or an explicit HOST override to something more restrictive like 127.0.0.1. Confirm both containers are on the same network and that you haven't set HOST yourself.

Do I need to set a token?

Yes, unless the container is only reachable by you. Browserless doesn't generate one for you: leaving TOKEN unset leaves every endpoint unauthenticated, including /function, which executes arbitrary Puppeteer code from the request body. Set your own value with -e "TOKEN=your-secret-value".

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.

Next steps

Was this page helpful?