VeilBrowserDocs

Cluster mode

Launch browsers on demand over a single WebSocket endpoint, load-balanced across your worker fleet.

Cluster mode turns a local-api worker into an on-demand browser server: your automation connects to wss://<host>/launch, a browser is spawned and licensed per launch, and CDP is piped straight back. No REST pre-step, works with stock Puppeteer / Playwright / any CDP client.

Enable it by running the worker with VEIL_SERVER_MODE=cluster.

Connect

cluster.ts
import puppeteer from "puppeteer-core";

const env = {
  VEIL_KEY: "<organization-api-key>",
  // Launch an existing cloud profile…
  profileId: "<profile-id>",
  // …or create one from criteria:
  // spec: { os: "win", browserVersion: 130 },
};

const url =
  "wss://connect.example.com/launch?env=" +
  encodeURIComponent(JSON.stringify(env));

const browser = await puppeteer.connect({ browserWSEndpoint: url });
const page = await browser.newPage();
await page.goto("https://example.com");
cluster.ts
import { chromium } from "playwright-core";

const env = { VEIL_KEY: "<organization-api-key>", profileId: "<profile-id>" };
const url =
  "wss://connect.example.com/launch?env=" +
  encodeURIComponent(JSON.stringify(env));

const browser = await chromium.connectOverCDP(url);
const page = await browser.contexts()[0].newPage();
await page.goto("https://example.com");
terminal
# List live browsers on a worker
curl "http://localhost:38923/browsers?simple=true"

Reconnect, VNC and devtools

Give a launch a stable referenceId to reconnect later, and address a specific browser for VNC / devtools:

  • wss://<host>/launch/<referenceId>?reconnect=true — reattach or launch.
  • GET /browsers/<id>/urls — worker-encoded URLs for CDP, devtools and VNC.
  • GET /browsers/<id>/json — the browser's CDP page list.

Behind the cluster-manager load balancer these URLs path-encode the worker id (/w/<workerId>/browsers/<id>/...) so follow-up requests always reach the worker holding that browser.

Load balancing

Launching is stateless; everything after is sticky to one worker. The cluster-manager OpenResty LB handles both:

  • Launchwss://connect.example.com/launch is spread across workers with least_conn (approximating capacity-aware selection). A worker at capacity returns 503 before the upgrade, and the LB retries the next one.
  • Sticky ops/w/<workerId>/... routes deterministically to a specific worker. Single domain, single certificate, no wildcard DNS.

Enable it by setting clusterProxyEnabled on the Cluster row and running Render & Reload. Set VEIL_WORKER_ID on each worker to match its FleetHost hostname.

Capacity & lifecycle

Bound per-worker concurrency with VEIL_MAX_BROWSERS. Reap idle/abandoned browsers with VEIL_AUTO_CLOSE=true (close on last client disconnect) and VEIL_IDLE_BROWSER_TIMEOUT=<seconds>.

Licensing is fail-closed

Every launch calls POST /v1/license/authorize on the cloud. If the cloud is unreachable or the org is at its concurrency limit, the launch is refused — already-running sessions are never killed. Budget for one cloud round-trip per launch.

Private images

The worker and cluster-manager images are pulled from the license-gated registry using your organization's API key, lowercased, as the path segment. The registry gate verifies it against the cloud before proxying to the upstream image:

terminal
docker pull registry.veilbrowser.net/<org-key-lower>/local-api:latest
docker pull registry.veilbrowser.net/<org-key-lower>/cluster-manager:latest

<org-key-lower> is your VEIL_KEY in lowercase (the install.sh script sets it for you as VEIL_ORG_KEY_LOWER). Registry gating is a convenience gate — runtime authorize is the real license enforcement.

Versioning & channels

Each image is versioned independently from its own source. Pick a channel by tag:

:X.Y

Auto-patch within a minor. Recommended for production.

:X.Y.Z

Pinned/reproducible. Use for strict change control.

:canary

Built from dev — pre-merge testing.

:beta

Prerelease line for opt-in testers.

The /v1 cloud API is a frozen contract to self-hosted workers: additive-only within v1; breaking changes ship as /v2 with an overlap window (workers supported within 2 minors / 90 days). Workers advertise their contract on /health and refuse to run against an incompatible cloud.

Deploy

Templates for single-host compose, Kubernetes and one-click clouds live in deploy/. See Upgrades for drain-then-roll worker updates.

Was this page helpful?

On this page