← All posts
Guide

Self-hosting TestVibe on your own infrastructure

Most teams should use the hosted cloud. TestVibe ships a self-hosted edition for the cases that need more than that: air-gapped environments, data-residency rules, or compliance requirements that rule out a hosted control plane. That's a different problem from keeping the app under test off the public internet alone — for that, a private runner is far less to operate (see below). This guide covers what actually runs where, how licensing works offline, and how to decide whether self-hosting is worth the operational weight.

Cloud, private runner, or self-hosted

These three options sit on a spectrum of how much you host, and they're easy to confuse.

OptionWhat you hostWhere your data lives
Cloud (SaaS)NothingTestVibe's cloud
Private runnerJust the runner imageApp in the cloud, test execution in your network
Self-hostedThe entire stackEntirely your infrastructure

The distinction that trips people up: a private runner is a feature of the cloud SaaS. You still sign in to the hosted app; only the test execution happens on a runner inside your network, so your app under test never has to be internet-reachable. Self-hosting is the opposite bet: you run the app itself.

If all you need is for test execution to stay inside your network, a private runner or a tunnel is far less to operate than the full stack. Reach for self-hosting when the whole product has to run on-prem, air-gapped deployments, strict data-residency rules, or compliance requirements that don't allow a hosted control plane.

What actually runs

Self-hosted TestVibe is delivered as a versioned Docker bundle, a docker-compose stack plus an .env template. A single docker compose up brings up a complete, private instance. The bundle is a handful of containers, each replacing a piece of the cloud platform:

ServiceRole
webThe TestVibe app (Wisej/.NET), what users open in a browser.
postgresThe application database, holding accounts, projects, tests, runs, and canonical test files.
migrateA one-shot job that applies the database schema, then exits.
blobserverObject storage for artifacts, screenshots, videos, traces, and run bundles.
runnerExecutes your Playwright test runs.
browserA browser host the app drives for AI generation and the assistant.

The web app talks to Postgres for data, the blob server for artifacts, the runner to dispatch test runs, and the browser host to drive AI generation. Both the runner and the browser host need network reach to your site under test, so make sure your application's URL is reachable from inside the stack.

Compose enforces a strict startup order so the app never boots against an unprepared database: Postgres comes up healthy, the schema job runs and exits cleanly, the blob server comes up healthy, and only then does the web app start. Data lives in named Docker volumes, so it survives docker compose down / up and upgrades. Removing those volumes deletes everything, so back them up before any destructive operation.

Installing it

The container images are private; you authenticate to the registry with a token TestVibe provides before pulling. From there the flow is short and repeatable:

# after authenticating to the registry and filling in .env
docker compose up -d

# confirm it came up
docker compose ps

The app and object storage should report healthy, and the schema job should show Exited (0). Then open the app at the public URL you configured (the default is http://localhost:3000).

Upgrades are the same two commands against new image tags. The schema job re-runs and applies only its new changes, since it's idempotent, then the app restarts on the new version, and your data in the volumes is untouched.

Configuration is environment-only

There's no configuration file to edit. Everything the app needs comes from environment variables set in the bundle's .env (or injected as container environment variables). Any secret can instead be provided as a file by setting {NAME}_FILE to a mounted path, which is how you wire in Docker or Kubernetes secrets without putting them in .env.

You only supply the secrets and your public URL; the compose file already wires up which provider and internal URLs each service uses. The required values:

# The five values you must set
TV_PG_PASSWORD=             # PostgreSQL password, shared by postgres, migrate, and web
TV_BLOBSERVER_SIGNING_KEY=  # signs the short-lived artifact URLs
TV_BLOBSERVER_ADMIN_KEY=    # authenticates server-to-server calls to the blob store
TV_RUNNER_KEY=              # shared secret so only your app can dispatch runs
TESTVIBE_PUBLIC_BASE_URL=   # the URL users reach the app at; also OAuth + webhooks

Generate strong, unique secrets for each, treat them like production credentials rather than reusing template defaults. Everything optional (GitHub/Google sign-in, email delivery, connecting source repositories) is off until you set it, and the app still runs without any of it. Without email configured, verification and password-reset links are written to the app's logs instead of sent, which is fine for a closed internal instance.

One difference worth calling out: billing is off on-prem. The cloud SaaS meters session-seconds and AI tokens and bills through Stripe. On a self-hosted instance, credit gating and token metering are disabled: there are no credits to buy and nothing is metered. Your only cost controls are your own AI provider's usage and the infrastructure you run.

The offline license model

A self-hosted instance is licensed with an offline, signed license. There's no phone-home, so it works fully air-gapped: the instance verifies the license locally against a key built into the app and never makes an outbound call. The license is bound to your specific install and names your organisation, so it can't be reused on a different instance, and it's perpetual by default.

You don't need a license to boot. The lifecycle has three states:

StateWhat happens
GraceNo valid license yet. The app runs normally and shows a warning. The window is a fixed number of days (default 14) and is persisted, so restarting doesn't reset it.
Read-onlyGrace expired with no license. Existing data stays fully viewable, but new test runs, generation, and load tests are paused until you activate.
LicensedA valid license is installed. Everything runs and the warning clears.

Read access is never blocked: you can always sign in and view projects, tests, and results. Only new chargeable work pauses. To activate, find the install id printed in the web container's logs on first boot (look for a [license] line), send it and your organisation name to TestVibe, and set the license string TestVibe returns as an environment value. The instance picks it up on its next periodic check or restart. If a license lapses later, a fresh grace window starts before read-only kicks in, so renewals get a buffer rather than an instant cutoff.

Powering AI on-prem

AI powers generation and the assistant only; authoring, running tests, results, and load testing all work without any model configured. You have two options for where the model lives. Supply a hosted Anthropic or OpenAI key for the simplest setup and strongest results, at the cost of sending generation prompts to that provider. Or point TestVibe at your own OpenAI-compatible endpoint (Ollama, vLLM, LM Studio, or similar) so nothing leaves your network. The bundle can even run a local model as an optional inference sidecar for a fully self-contained instance.

The one hard requirement: the model must support tool/function calling. TestVibe generates tests by having the model call browser tools step by step, so a model without tool-calling support will stall during generation. Choose a tool-calling-capable coding model, and plan on a GPU for acceptable speed.

Should you self-host?

Self-hosting trades operational simplicity for control. If your app under test is reachable from the internet, the cloud is genuinely less to run. If only execution needs to stay private, a private runner gets you there for a fraction of the effort. Reach for the full self-hosted stack when air-gapping, data residency, or compliance leave you no other option, then the offline license and local-model path let TestVibe run with nothing leaving your network. Read the full self-hosting docs at docs.testvibe.com, or get early access to try the hosted edition first.

Early access

Ready for tests that write themselves?