Self-hosted

Run TestVibe on your own infrastructure

Your projects, test runs and generated code stay inside your network. Sign in with the email on your subscription to download the release and activate your license.

We send a 6-digit code. Access is limited to emails on an active self-hosted subscription — talk to sales if you do not have one yet.

We sent a code to . It expires in 10 minutes.

Self-hosted

Your deployment

Latest release

Loading…

Installation guide

Every step in full: sizing, the import, what a healthy first boot looks like, creating the first application user, upgrading, backup, and troubleshooting keyed to the symptoms first installs actually hit.

Open the guide

Install

One host runs the whole stack: the app, the test runners, a browser pool, Postgres and object storage. Nothing calls out to us at runtime. All four shapes run the same application.

These are outlines, not the procedure — each links to the full steps. And do not stop at the console checklist: a fresh install has no application accounts, and the console password is not one. You are finished when Mail is configured and the first application user exists. That walkthrough →

The shapes do not always sit at the same version. Containers and the chart ship with every release; appliance images are built separately and can lag. Newest appliance: 0.4.3. Newest containers and chart: 0.4.7.

One Linux host with Docker Engine and Compose v2. The fastest way to evaluate.

  1. Unpack the bundle. Copy .env.example to .env and fill in the five required values listed on the Configure tab.
  2. docker login testvibe.azurecr.io — the images are private.
  3. docker compose up -d. Then open the console on port 5120, the app on port 3000, and work through the guide's first-boot steps.

Full steps →

An existing cluster. The chart ships inside the same bundle under kubernetes/, and is also published as an OCI artifact.

  1. kubectl -n testvibe create secret docker-registry testvibe-registry --docker-server=testvibe.azurecr.io --docker-username=<token-name> --docker-password=<token-password>
  2. helm registry login testvibe.azurecr.io, then helm install testvibe oci://testvibe.azurecr.io/charts/testvibe --version 0.4.7 --set global.imagePullSecrets[0].name=testvibe-registry plus the five secrets.* values on the Configure tab.
  3. kubectl -n testvibe port-forward svc/testvibe-admin 5120:80 — the console gets no ingress by default. Then work through the guide's first-boot steps.

Full steps →

A bootable VM running the whole stack natively under systemd. No Docker, and it reaches no registry at all — the option for an air-gapped site.

  1. tar -xf testvibe-0.4.3.vhdx.tar, then zstd -d testvibe-0.4.3-os.vhdx.zst testvibe-0.4.3-data.vhdx.zst
  2. Create a Generation 2 VM and attach both disks. An OS disk on its own boots to a stack that never starts.
  3. Boot it. It prints its addresses and a generated console password to the VM console. Open port 5120, then work through the guide's first-boot steps.

Full steps →  ·  Never discard disk 2: an upgrade ships a new OS disk and reuses your data disk.

The same appliance, packaged for VMware and VirtualBox. Import and boot.

  1. Import testvibe-0.4.3.ova. Both disks are inside it already.
  2. VirtualBox only: set firmware to EFI before the first boot, or it stops at “No bootable medium found!”VBoxManage modifyvm "TestVibe-0.4.3" --firmware efi.
  3. Boot it. It prints its addresses and a generated console password to the VM console. Open port 5120, then work through the guide's first-boot steps.

Full steps →  ·  Never discard disk 2 — it holds your database, artifacts, identity and license.

Deployments used

Subscription

Activate an install

Your instance generates its own install ID on first boot. Find it in the admin console under License, paste it here, and we issue a license bound to it. Re-issuing for an install you already activated is free and does not use another deployment.

Checksum — the console shows the same value if the paste arrived intact.

Your deployments

Registry access

The TestVibe images are private, and this is the most common reason a first install stops before it starts — the error (denied, manifest unknown, or a pod in ImagePullBackOff) does not say “you are not signed in”. We issue you your own token: pull-only, scoped to your subscription, revocable on its own.

Where you run itWhat to do
Docker Compose docker login testvibe.azurecr.io -u <token-name>, then paste the token as the password. Once per host.
Kubernetes Create a pull secret and name it in your values:
kubectl -n testvibe create secret docker-registry testvibe-registry --docker-server=testvibe.azurecr.io --docker-username=<token-name> --docker-password=<token-password>
then --set global.imagePullSecrets[0].name=testvibe-registry.
Appliance (VHDX / OVA) Nothing. The appliance runs the stack natively from software already inside the image — it never pulls from a registry.

What you must set before first start

Five values, in .env. Miss one and docker compose up -d aborts naming it, rather than booting into a broken state. Four are just long random strings; generate each with openssl rand -hex 32. On Kubernetes the same five are the secrets.* chart values, and the chart refuses to render without them.

VariableWhat it is
TV_PG_PASSWORD required The password for the bundled Postgres.
TV_BLOBSERVER_SIGNING_KEY required Signs the object-store links the app hands your browser and the runners — traces, artifacts, asset previews.
TV_BLOBSERVER_ADMIN_KEY required The app's own server-to-server credential to that object store. A different random string.
TV_RUNNER_KEY required Shared by the app, the test runner and the generation browser, so only the app can dispatch runs or drive a browser. One value, wired to all three.
TV_ADMIN_PASSWORD required Signs you into the admin console. Held in the environment, not the database, so a broken single sign-on cannot lock you out of the place you would go to fix it. Use TV_ADMIN_PASSWORD_FILE to mount it as a secret instead.
TV_BOOTSTRAP_TOKEN set this too This is how you get in. A fresh install has no application accounts, and TV_ADMIN_PASSWORD is not one. Set this and the console's Users panel can create the first application user, already verified — which matters, because before you have a mail relay the sign-up verification link is only printed to the web container's log. Leave it unset and the endpoint does not exist at all. Generate with openssl rand -base64 32. It cannot become a way in later: the app refuses it once any account exists.
TV_SIGNUP_MODE Who may register after the first user: invite (the self-hosted default), open or closed. It only affects accounts that do not exist yet.
TV_LICENSE Optional — you can paste the license in the console instead. The environment wins over the console.
TESTVIBE_SECRET_PROTECTION_KEY Leave this unset. It encrypts stored project secrets. The app now generates one into its own database on first use, and both the app and the console read it back from there — so there is nothing to set, nothing to keep in step, and nothing to copy beyond the database backup you are already taking. .env.example ships it commented out, and the Helm chart's values.yaml says to leave it empty. Setting one is permanent: change it and every stored project secret becomes undecryptable.

Everything else is configured in the console

The settings below are edited in the admin console and take effect within a minute — no restart. The console stores them in the database.

The variable names are shown because some of these can also be pinned in the environment, and a pinned value then shows as locked in the console. On Compose that means adding the variable to the web service's own environment: block in docker-compose.yml — putting it in .env alone does nothing, because Compose only passes on what the compose file lists. On Kubernetes the equivalent is web.extraEnv. Each group below says which way it goes.

Mail console only

Sign-in verification, invitations, password resets and run notifications. Point it at your own relay — Exchange, Microsoft 365, Google Workspace, Postfix, SES.

Do not put these in .env. The shipped .env.example and docker-compose.yml contain no TV_SMTP_* variables at all, and the web service forwards none of them, so a TV_SMTP_HOST in .env has no effect. The Helm chart carries no SMTP values either. The only mail key Compose does pass through from .env is TESTVIBE_MANDRILL_API_KEY.
TV_SMTP_HOST console onlyRelay hostname. Required for mail to work. Not read from .env on Compose.
TV_SMTP_PORT console only587 (STARTTLS) or 25. Port 465 is not supported — use 587, which every mainstream relay also offers.
TV_SMTP_USE_TLS console onlyOn by default. Turn off only for an internal relay with no TLS.
TV_SMTP_USERNAME console onlyBlank if the relay authorises this host by IP.
TV_SMTP_PASSWORD console onlyApp password or service-account token. Microsoft 365 and Google Workspace both need one of these, not a user's own password.
TESTVIBE_EMAIL_SENDER_ADDRESS console onlyFrom address, on a domain your relay owns. Required for mail to work. Not read from .env on Compose.
TESTVIBE_EMAIL_SENDER_NAME console onlyDisplay name. Defaults to TestVibe.

Press Send test in the console. It pushes a real message through these settings, so a wrong password surfaces there instead of as a colleague's missing invitation. Configure mail before anyone opens the app: sign-up sends a verification link, and with no relay that link is only printed to the web container's log.

Single sign-on console or .env

Local email and password always works. Add your identity provider over OpenID Connect — Microsoft Entra ID, Okta, Keycloak, Auth0 and Ping are all standard OIDC. Register TestVibe as a confidential web application; the console shows you the exact redirect URI to paste. The Compose stack does forward all of these from .env, so pinning them there works.

TV_OIDC_ISSUER requiredThe issuer URL, not the authorize URL — we read /.well-known/openid-configuration from it.
TV_OIDC_CLIENT_ID requiredFrom your provider's app registration.
TV_OIDC_CLIENT_SECRET requiredStored encrypted.
TV_OIDC_LABELButton text: “Continue with this”.
TV_OIDC_SCOPESDefaults to openid email profile.
TESTVIBE_GITHUB_CLIENT_ID / _SECRETOptional GitHub sign-in.
TESTVIBE_GOOGLE_CLIENT_ID / _SECRETOptional Google sign-in. Register the callback with a trailing slash.

SAML is not supported. Every provider listed above speaks OIDC.

AI models console or .env

Which models write and heal your tests. Not a list of variables: in the console's AI models page you connect a provider, then tick the individual models your users may pick. Those are two separate acts, so a new model never appears in anyone's picker until you say so.

Anthropic and OpenAI are the two engines TestVibe generates with, direct. OpenCode and OpenRouter are gateways — many vendors' models behind one key. Custom is anything speaking the OpenAI chat-completions API: vLLM, Ollama, LM Studio, LiteLLM, or Azure OpenAI behind a gateway. That is how you keep prompts and page content inside your own network.

Each provider gets a Test connection button, so a wrong key surfaces there rather than as a failed generation. A model must support tool-calling or generation stalls. Keys are stored encrypted and never shown back to you. ANTHROPIC_API_KEY, OPENAI_API_KEY and TV_LLM_* are forwarded from .env on Compose. AI providers in the guide works through the choice.

Capacity console only

Set these in the console. The Compose stack forwards none of them from .env.

TV_SANDBOX_LOCKED_MAXConcurrent test runs; single features and whole suites share this budget. Default 10.
TV_SANDBOX_GENERATION_MAXConcurrent AI generations. Default 10. Each drives a real browser, so this usually decides how much CPU the host needs.
TV_SANDBOX_LOAD_MAXConcurrent load-test machines, not runs. Default 20. A browser-mode run takes one machine per 20 virtual users; k6 runs pack far more per machine.
TV_SANDBOX_ROAMING_MAXConcurrent assistant sessions — the chat and explore browser only. Default 10. It does not control test runs.
TV_SANDBOX_ACQUIRE_TIMEOUT_SECONDSHow long queued work waits for a slot before failing with capacity_unavailable. Default 1800 (30 minutes).

These are admission caps. Work that does not fit queues rather than being rejected, so raising one above the capacity you deployed just moves the queue. The runner and browser containers each stay at one replica: run state lives in the process that accepted the job, so the chart refuses runner.replicaCount or browser.replicaCount above 1, and Compose --scale is not supported. Add capacity by giving that one container more CPU. Capacity & concurrency works through the sizing, with a section each for test runs, generation and load testing.

How updates work

Your admin console checks this same release list and tells you when you are behind. It does not update itself, and holds no privileges to — it runs beside an app that executes generated test code. You run the update yourself.

The new bundle is the upgrade. Every image reference in your docker-compose.yml names an exact version, which is why docker compose pull on its own never moves you forward: it re-pulls the tags already pinned in your file. Put the new bundle's docker-compose.yml in place and keep your .env exactly as it is.

cd /opt/testvibe unzip ~/testvibe-selfhosted-v0.4.7.zip -d /tmp/testvibe-0.4.7 cp /tmp/testvibe-0.4.7/docker-compose.yml ./docker-compose.yml docker login testvibe.azurecr.io docker compose pull docker compose up -d

Verify the download against SHA256SUMS first, and diff the new .env.example against your .env — a release that adds a required variable adds it there. Migrations run automatically on start, and your data lives on separate volumes that survive the upgrade. Read the “before upgrading” notes below first.