Self-hosted browsers for AI agents and automation

Run browsers anywhere. Watch them live. Drive them with any agent.

browserbay runs isolated Chromium browsers on infrastructure you control: a VPS, a Raspberry Pi, Kubernetes. Drive them with Playwright, Puppeteer or any agent over the DevTools protocol, and show a live view in your own app so a person can watch and take over.

node-12 of 6 slots in use
task-42
free
agent-7f3a
free
free
free
Slots are leased, so a busy box frees itself.

How it works

Ask, drive, hand back

  1. Ask. Acquire a slot with a run id and a lease. If the box is full it waits, then tells you.
  2. Drive. Connect Playwright, Puppeteer or anything that speaks the DevTools protocol.
  3. Hand back. Release it. If your process dies first, the lease closes the browser anyway.

Clients for Node, Python and Go. Anything that speaks HTTP and WebSocket works too.

Quick start →
import { chromium } from 'playwright'
import { createBrowserBay } from
  '@degoke/browserbay/client'

const box = createBrowserBay({
  url: 'https://browsers.example.com',
  apiKey
})

const slot = await box.acquire({
  runId: 'task-42',
  maxLifetimeSeconds: 1800
})
try {
  const browser = await chromium.connectOverCDP(
    slot.cdpUrl,
    { headers: slot.headers }
  )
  const context = browser.contexts()[0]
  const page =
    context.pages()[0] ?? (await context.newPage())
  await page.goto('https://example.com')
} finally {
  await slot.release()
}
shop.example.com/checkoutlive
Pay now
driver Playwright · stepped awayviewer a person · steering

Illustration. You build the viewer; the box supplies the stream.

Live view

Watch it. Steer it. Hand it back.

A slot takes more than one connection. Your service drives with Playwright. A person opens a second connection, watches a stream of frames, and clicks and types in the same page. When they step away, your service reconnects and finds the pages as they left them.

  • A second connection, not a takeover. The viewer does not push your driver off the slot.
  • People never get DevTools. Your viewer turns their clicks and keys into input events. The slot token stays on your server.
  • Priced in bandwidth. A page that repaints every frame costs about 2 MB/s per watcher. A still page costs almost nothing.
// On your server: a second connection to the slot
const ws = new WebSocket(slot.cdpUrl, {
  headers: slot.headers
})

// Ask Chromium for frames, and send them on
await send('Page.startScreencast',
  { format: 'jpeg' }, sessionId)

// Turn the person's click into input
await send('Input.dispatchMouseEvent',
  { type: 'mousePressed', x, y,
    button: 'left', clickCount: 1 }, sessionId)
Read the live view guide →

What you get

Built to be left running

Isolated

Every browser is its own context

Each slot has its own cookies, storage and cache on a shared Chromium. A client sees its own pages and nothing else on that Chromium. This is tested against real Chromium, including attempts to reach another slot.

Anywhere

Deploys anywhere, dials out

Run it on a VPS, a container platform or a Raspberry Pi. Nodes connect out to the relay, so a node can sit behind NAT, and no DevTools port is open anywhere.

Scales

Add capacity by adding nodes

Start with one container. When you need more browsers, run more nodes, on any machine, and the relay schedules across them.

Contained

Hard to leak, easy to clean up

Three separate secrets. A lease on every browser, so a crashed client cannot hold one forever. A protocol version check between relay and nodes.

Scope

The box and nothing on top

browserbay gives you a browser and stops there. No agent loop, no model calls, no crawler, no screenshot endpoint. That keeps the box small, and it lets one box serve a test suite, a scraper and an agent at the same time.

Deploy

Start with one container

The default image is a complete box: the relay and a Chromium node together. When you want browsers on several machines, run one relay and add nodes as you need them, anywhere. On Kubernetes, a Helm chart runs the relay once and the nodes as their own Deployment, so they scale on their own.

Kubernetes →Relay and nodes →
export BROWSERBAY_API_KEY="$(openssl rand -base64 24)"

docker run -d --name browserbay --restart always --init --stop-timeout 130 \
  -p 127.0.0.1:8788:8788 -e BROWSERBAY_API_KEY \
  ghcr.io/degoke/browserbay