Private Runner¶
Private Runner lets VisualRunner monitor environments our capture servers can't reach on their
own — a VPN-only staging site, an internal admin tool, a service bound to localhost on your
CI box. You run a small Docker container inside your own network; it pulls capture jobs from
VisualRunner over an outbound-only connection and sends back the screenshots.
Private Runner is an Enterprise feature, enabled per workspace. It isn't self-serve — contact us (
support@visualrunner.comor your account contact) to have it switched on.
How it works¶
- The runner makes only outbound HTTPS connections to VisualRunner. You don't open any inbound port or poke a hole in your firewall.
- It runs inside the network that already has access, so it never needs your VPN credentials. If an environment also sits behind a login, the cookie / header / basic-auth credentials you configured on it are sent to your runner (over its own outbound connection) and applied in the browser for that capture — your encryption key never leaves VisualRunner.
- It only ever does one thing: run the screenshot-capture jobs you've already configured (pages × environments × languages × viewports). It can't be used for anything else.
- It uses the same browser engine as our own capture servers, pinned to the same version, so a screenshot taken by your runner matches one taken by us.
What it covers¶
A Private Runner is set per environment (Environments tab → Capture delivery), and it only changes where the screenshot capture for that environment runs:
| Runs on your runner | Still runs on VisualRunner's platform (egress IP 157.230.2.191) |
|---|---|
| Manual captures for that environment | Uptime checks — there is no runner path for uptime at all |
| Scheduled screenshot Monitors for that environment (each run's capture + all its QA checks) | Discovery scans |
| The environment connectivity test on the Environments tab |
A monitor on a runner-delivered environment is created and enabled normally — VisualRunner skips the platform-side connectivity check that would otherwise fail for a host our servers can't reach, because your runner captures it instead. As a safety net, if a runner-delivered monitor's scheduled runs capture nothing three times in a row (runner offline, or it can't reach the site), VisualRunner auto-pauses that monitor with a message telling you to check the runner. Fix the runner and re-enable the monitor.
So a VPN-only staging site can be captured and screenshot-monitored through a runner with nothing to allowlist — but uptime monitoring on that same site still needs VisualRunner's egress IP allowlisted to reach it directly.
Setup¶
- We enable the feature on your workspace.
- In Workspace settings → Runners, click Register a runner. You'll get a token
(
vr_runner_…) shown once — copy it now; you can't see it again (rotate to get a new one). - Run the container on a host inside your network that can reach both
https://visualrunner.com/apiand the internal sites you want captured:
bash / zsh (Linux, macOS, WSL):
docker pull visualrunner/runner:latest
docker run -d --restart unless-stopped --name visualrunner-runner \
-e PLATFORM_URL=https://visualrunner.com/api \
-e RUNNER_TOKEN=vr_runner_xxxxxxxxxxxxxxxx \
visualrunner/runner:latest
Single line:
docker run -d --restart unless-stopped --name visualrunner-runner -e PLATFORM_URL=https://visualrunner.com/api -e RUNNER_TOKEN=vr_runner_xxxxxxxxxxxxxxxx visualrunner/runner:latest
PowerShell (Windows):
docker pull visualrunner/runner:latest
docker run -d --restart unless-stopped --name visualrunner-runner `
-e PLATFORM_URL=https://visualrunner.com/api `
-e RUNNER_TOKEN=vr_runner_xxxxxxxxxxxxxxxx `
visualrunner/runner:latest
Single line:
docker run -d --restart unless-stopped --name visualrunner-runner -e PLATFORM_URL=https://visualrunner.com/api -e RUNNER_TOKEN=vr_runner_xxxxxxxxxxxxxxxx visualrunner/runner:latest
The container is outbound-only — it opens no ports and needs no inbound
firewall rule. To update it later: docker pull visualrunner/runner:latest
then recreate the container. Pin to a fixed version (visualrunner/runner:v0.1.0)
if you'd rather update on your own schedule — we'll tell you the current version.
Optional environment variables: RUNNER_CONCURRENCY (1–16, default 1 — see
Host requirements & concurrency),
RUNNER_NAV_TIMEOUT_MS (default 30000), RUNNER_HEARTBEAT_MS (default 60000).
If your team can't pull from Docker Hub, ask us for an image tarball
(docker load < visualrunner-runner.tar.gz) instead.
- In your project's Environments tab, set the environment's Capture delivery to your runner instead of "Platform worker". Monitors and manual captures for that environment now run on your runner.
Once the container is running and has sent its first heartbeat, the runner shows as online in Workspace settings.
Host requirements & concurrency¶
The runner image is a headless-Chromium (Playwright) container. Each concurrent capture slot is a fresh browser context that spikes one CPU core while a page renders and holds ~2 GB of RAM. Size the host to the number of captures you want to run at once — plus ~5 GB free disk (the image is ~2 GB, plus working space) — and to whether the vCPUs are dedicated or shared.
Dedicated CPU¶
AWS c7/c6, DigitalOcean CPU-Optimized, GCP c2, Hetzner CCX — you get the whole core, so
budget ~1 vCPU per slot plus one for the OS and the agent.
RUNNER_CONCURRENCY |
Suggested vCPU | Suggested RAM | Captures at once | ~100 captures |
|---|---|---|---|---|
| 1 (default) | 2 vCPU | 4 GB | 1 at a time | ~50 min |
| 2 | 3 vCPU | 6 GB | 2 at once | ~25 min |
| 4 | 5 vCPU | 10 GB | 4 at once | ~13 min |
| 8 | 9 vCPU | 18 GB | 8 at once | ~7 min |
| 16 (max) | 17 vCPU | 34 GB | 16 at once | ~3 min |
Shared / burstable CPU¶
AWS t3/t4g, DigitalOcean Basic, GCP e2, Hetzner CX/CPX — each vCPU is a scheduled slice of
a core with a burst-credit budget. Roughly double the vCPU count, and treat the times as
best-case: a long backlog burns through the credits and then throttles.
RUNNER_CONCURRENCY |
Suggested vCPU | Suggested RAM | Captures at once | ~100 captures |
|---|---|---|---|---|
| 1 (default) | 2 vCPU | 4 GB | 1 at a time | ~50 min |
| 2 | 4 vCPU | 6 GB | 2 at once | ~25 min |
| 4 | 8 vCPU | 10 GB | 4 at once | ~13 min |
| 8 | 16 vCPU | 18 GB | 8 at once | ~7 min |
| 16 (max) | 32 vCPU | 34 GB | 16 at once | ~3 min |
RAM is the same in both tables — it tracks the number of slots, not the core type. Heavy pages (large images, video, huge DOMs) need 3–4 GB per slot instead of 2.
One job vs. many¶
One slot runs captures one after another. RUNNER_CONCURRENCY = N runs N at once, so a backlog
clears roughly N× faster. A single capture is typically 10–40 seconds of wall-clock time
depending on page weight and RUNNER_NAV_TIMEOUT_MS (default 30 s). The ~100 captures column
is that backlog at the ~30 s midpoint, run RUNNER_CONCURRENCY-wide (100 / N × 30 s) —
planning estimates, not guarantees.
Running more than one job¶
Concurrency is entirely under your control. To go from one job to several, add
-e RUNNER_CONCURRENCY=<n> (1–16) to the docker run command and restart the container — the
agent then runs <n> independent poll → capture → upload workers. Nothing has to be requested,
granted, or reconfigured on the VisualRunner side, and concurrency is not metered or billed:
the Private Runner feature flag only governs whether runner delivery is available at all. The
runner reports its maximum concurrency to VisualRunner on every heartbeat, and the platform
never hands it more work in parallel than that.
Scaling past one host¶
The per-runner cap is 16. Beyond that, run a second runner container on another host and point different environments at different runners (Environments tab → Capture delivery). One environment is served by exactly one runner today.
If the host is undersized you'll see capture timeouts, Chromium processes killed for memory, or
slow lease turnaround — lower RUNNER_CONCURRENCY or add CPU/RAM.
What's supported today¶
- Authenticated environments work. Cookie, header, and HTTP basic-auth credentials you set on an environment are delivered to your runner over its outbound-only connection and applied in the browser, so VPN-only sites that also sit behind a login are captured normally. Your runner never receives your encryption key — VisualRunner decrypts the credentials just before handing out each job.
- Runner captures count toward your plan's automated-capture allowance and storage, exactly like captures run on our servers — a private runner is a different place the capture runs, not a separate quota.
- Full QA checks. Runner captures produce the screenshot, visual comparison, extracted text for search, and the same text-length, accessibility, console-error, brand, and missing-translation checks as server-run captures.
- If a runner goes offline mid-job, VisualRunner automatically retries the capture on your next online runner (up to 3 attempts) before marking it failed. A monitor whose runner has gone away pauses itself with a clear reason rather than failing every check.
Managing runners¶
- Rotate token — issues a new token and invalidates the old one immediately. Update the
container's
RUNNER_TOKENand restart it. - Revoke — permanently disables the runner. Any job it was holding is released and retried elsewhere.
- A runner is a workspace resource — it keeps working if the person who registered it leaves the workspace.
See also¶
- Monitors
- Captures & Comparisons
- Enterprise — custom plans and the other hand-sold capabilities
- VisualRunner Enterprise Extension