Skip to content

Enterprise deployment

The whole platform, on machines you own.Installed by one command.

Every other vendor in this category runs the browsers, so none of them can hand you the software that operates them. This page is that software: what the installer puts on a host, how the fleet is watched, what happens to a configuration that does not parse, how a worker is replaced without dropping the work already on it, and who is allowed to do any of it.

Enterprise starts at $5,000. A trial or a demo is arranged with sales.

Install
$ curl -fsSL https://veilbrowser.net/install.sh | VEIL_KEY=<org-key> bash

One value to supply. Everything else is derived on the host.

Install
1 command · 1 input
Setup
5 steps
Fleet poll
every 30 s
Roles
admin · operator · viewer

01Install

One command

What the installer puts on the host

One line, and one value you supply. The docker group, the internal key and the auth secret are derived where it runs. Then a five-step wizard in a fixed order, ending with a configuration that is rendered, validated and stamped.

The five steps

  1. 01

    Administrator account

    Created during setup, so no image ships with a default password and there is nothing to rotate afterwards.

  2. 02

    Cluster name and hostnames

    The names the edge will answer for. Until a hostname exists the edge stays deliberately open, so an evaluation install is reachable on its IP before DNS is wired up.

  3. 03

    TLS

    Cloudflare with a self-signed or Cloudflare-issued origin certificate, a PEM key pair you supply, or Let's Encrypt issued and renewed inside the edge process.

  4. 04

    The first worker

    Joined to the cluster network. Browsers execute here, and the same image is what a larger fleet is made of.

  5. 05

    Review and finish

    The configuration is rendered, validated, swapped in and stamped complete. That stamp is what turns the hardening on.

Until the last step

Port 80 serves the wizard and the Cloudflare origin lock is deferred, because the operator driving setup is usually connected straight to the host and a lock applied mid-flow would shut them out of their own installation. The finishing render drops the grace and restores the strict catch-all.

Containerveil-cluster-managerWorkersAutomationPuppeteer, PlaywrightCDP over WebSocketOperatorsManager interfaceTLS, session authwss /launchhttpsEdge, OpenRestyTLS, routing, Lua:443 · :80Fleet manager, BunCluster, fleet, operations127.0.0.1:4180loopbackveil-cm-updatercompose profile: updater, off by defaultproxyveil-worker-01:38923 · /launch · /healthveil-worker-02:38923 · /launch · /healthveil-worker-03:38923 · /launch · /healthFurther workers, same shapeGET /fleet/status · every 30 sVeilBrowser cloudAccounts, plans, licencereached outbound only
Fig. 01The shipping topology with the ports each process binds. The self-update component is drawn dashed because the default installer does not enable it.

The installer, the wizard and every default it writes are public documentation, so a deployment can be read end to end before anyone asks you for a decision.

02Fleet health

Fleet health

A dead host and a dead API are not the same incident

Every registered host is polled every 30 seconds. A status request that fails is treated as a question rather than an answer: the manager opens a plain TCP connection to the probe port before deciding what is wrong, and records which of the two it found.
Schedulerfleet-poll*/30 * * * * *Probe 1GET /fleet/statusx-api-key · timeout 5 s200, body parsedtimeout or errorProbe 2TCP connectprobe port · timeout 2 sconnection acceptedrefusedSnapshotreachablemetrics storedSnapshotapi-deadhost up, API silentSnapshothost-downno TCP at all30 s30 s30 severy host, every tick

Recorded

reachable

reachable: true · activeSessions, load and version stored

The status body is kept as the snapshot, so the fleet view is never older than 30 seconds.

Fig. 02Select a case to follow the path the poller takes.

Shipped intervals

Poll interval
every 30 s, every host
First probe
GET /fleet/status, 5 s timeout
Second probe
TCP connect, 2 s timeout
Recorded as
reachable, api-dead or host-down

A host you stopped on purpose is skipped rather than reported, so a maintenance window does not arrive looking like an outage. A snapshot is also written the moment a mutation finishes, so the fleet view does not wait for the next tick to catch up.

Which of the two failures you are looking at decides whether the next step is a restart or a trip to the host, which is the difference this poll exists to make.

03Config changes

Load balancer changes

A rejected config never reaches traffic

Changing what the edge serves is one operation with five steps: render from the database, diff against the live file, hand the candidate to the same binary that will serve it, move it into place with a single rename, then reload. Validation failing ends the operation with the previous file still answering requests.
Candidate configStep 1Renderfrom databaseStep 2Difflive vs nextStep 3Validateopenresty -tStep 4Swapatomic renameStep 5Reloadsignal, no restartnginx.conf.stagingcut-overone rename() callAnswering requestsprevious config, still servingnew configno interval in which nothing is serving
operation
kind: "render-config"
status
succeeded
authority
admin only
Fig. 03Both outcomes against the same serving lane. The lower lane never has a gap.

What governs it

Validation
the serving binary, against the staged file
Cut-over
one rename() call
On failure
live file untouched, parser output kept
Authority
admin only

Reload signals the running process rather than restarting it, so a change is picked up without dropping open connections. The rename is on one filesystem, which is what makes the swap atomic rather than nearly atomic.

Before you apply anything

A preview renders the candidate and diffs it against the live file without touching either. An operator can produce that diff; applying it is an admin action.

The failure mode most reviews ask about is the one where a config change takes the edge down. Here it cannot, because the file that fails validation is never the file that serves.

04Routing

Where a launch lands

One endpoint, least connections, three tries

Automation points at a single URL. A launch goes to the worker holding the fewest open connections, because a live browser is a long-lived debugger connection and fewest connections is the closest available reading of most free capacity. If a worker rejects at capacity, up to three are tried before the launch is reported as failed.
First connectionAutomationpuppeteer.connectone endpointEdge route/launchupstream cluster_workersleast_connproxy_next_upstream error timeout http_502 http_503proxy_next_upstream_tries 3veil-worker-01open connections 12veil-worker-02open connections 3selectedveil-worker-03open connections 9Reconnects and session actionsSame client/w/veil-worker-02/stable per-worker pathveil-worker-02
Fig. 04Connection counts are an example. Selection is by open connections, and up to three machines are tried before a launch fails.

Routes the edge answers

/launch
A new session, placed on the least busy worker
/launch/:referenceId
A new session against a stored profile
/browsers/:id
Attach to a session already running
/w/<worker-id>/
Pinned to one worker, for reconnect, remote view and devtools

Shipped defaults

Selection
least connections, not round robin
Retry on
error, timeout, 502, 503
Upstreams tried
3 before failing
Marking a worker down
2 failures, 15 s window

When balancing would be wrong

Reconnecting to a browser that is already running has exactly one correct destination. Those requests carry the worker id and are routed by it rather than balanced, so a reconnect, a remote view or a devtools session reaches the machine holding the session. An id the edge does not know is refused rather than guessed at.

Draining or stopping the last worker leaves the pool empty. That case renders a placeholder the edge will never select, so requests get a clean gateway error and the edge still reloads, rather than the whole configuration being refused.

Capacity grows by pointing another machine at the same endpoint, so the automation you already wrote does not learn about it.

05Updates

Worker updates

Drain, roll, then prove it is healthy

An update takes the worker out of the launch pool before anything else happens, waits up to 120 seconds for the sessions already running, pulls the new image before tearing anything down, and does not receive traffic again until it reports healthy.

Operation steps

step ids as recorded: drain, sessions, pull, replace, address, health, re-enable, refresh

Launch poolworker-01worker-02held outworker-03Live sessions on worker-024 still runningdrain budget 120 sRunning imagepreviousnewHealthhealthy

01 Drain trafficThe worker is taken out of the launch pool first, so no new session can land on a machine that is about to be replaced.

Fig. 05Select a step to read the worker's state at that point. Step names are the ones written to the operation record.

Budgets and order

Session drain
up to 120 s
Health budget
120 s after start
Order
pull, then replace
If health does not return
rollback to the previous image

A roll that fails re-enables the host either way, so an operation that goes wrong never leaves a worker permanently drained. The replacement keeps the same identity, labels and install volume, and its address on the cluster network is read back rather than assumed.

Updating a fleet is the operation that most often turns into an outage, so it is the one with the most published detail here.

06Audit and roles

Attribution

Every mutating action is a record you can read

Applying a config, rolling a worker, issuing a certificate: each opens a record before it starts, streams its log while it runs, and closes with a status and an exit code. The record carries the user who started it, and it lives in the manager's own database on your host.
GET /api/operations/:id/log/streamtext/event-stream
  1. 15:04:02steprender started
  2. 15:04:02log1 server block, 3 upstream servers
  3. 15:04:02steprender ok
  4. 15:04:02stepvalidate started
  5. 15:04:03logconfiguration file test is successful
  6. 15:04:03stepvalidate ok
  7. 15:04:03stepswap started
  8. 15:04:03lognginx.conf.staging renamed onto nginx.conf
  9. 15:04:03stepswap ok
  10. 15:04:04stepreload started
  11. 15:04:04logreload signal accepted
  12. 15:04:04statussucceeded

Example stream from a config apply. Four event types are sent: log, step, progress and status.

What a record holds

Kind and inputs
What was attempted, and against which cluster.
Attribution
The account that started it.
Log
The full output, written to a file and streamed over server-sent events while the operation runs.
Outcome
A status, an exit code and a finish time. A failed operation keeps the output that explains why.

Three roles, enforced on the API

Ranked rather than orthogonal: admin outranks operator, operator outranks viewer. The check runs in the request handler, so it holds for anything holding a session against the API and not only for the console.

Capabilities available to the admin, operator and viewer roles
Capabilityadminoperatorviewer
Read the fleet, operations and configurationAllowedAllowedAllowed
Change cluster and fleet settingsAllowedAllowedNot allowed
Stop, start and update workersAllowedAllowedNot allowed
Preview a rendered configAllowedAllowedNot allowed
Apply a rendered configAllowedNot allowedNot allowed
Manage users and TLSAllowedNot allowedNot allowed

If a questionnaire is waiting on the other side of this decision, the security page answers it in the same detail rather than in a call.

07Limits

Not finished

Two things this page will not pretend

Stated here, in the same typeface as the capabilities above, because the alternative is you finding them in week three of an evaluation.
  • Not yet

    Remote worker provisioning is not automated

    The console does not stand up a worker on another machine for you. Workers on the manager's own host are created and replaced for you. A worker on a second machine is started there and then registered, so adding a host is still a task with a person in it. Everything after registration, including rolling and updating that worker, is automated.
  • Off by default

    The self-update component is not enabled

    The piece that replaces the manager's own container ships as a compose profile the default installer does not turn on. Until you enable it the manager will not update itself, so moving it to a new version is something you do rather than something that happens.

Both are operational rather than structural. Neither changes what the platform does once it is running, and both are on a roadmap we would rather walk you through than have you discover.

Bring the fleet you intend to run.

How many machines, in whose data centre, behind which proxies. The first call ends with a number and a demo, and every interval on this page can be checked against the documentation before it starts.

To install
One command, then five steps
Entry
Enterprise starts at $5,000
To read first
The installer and every default, documented publicly

Every interval, timeout and retry count on this page is the shipped one.