Self-hosted · Configuration

Licensing

How a self-hosted TestVibe install gets licensed, what happens while it is not, and why rebuilding the box does not cost you a deployment. Sign in with the email on your subscription — the same sign-in as the download portal.

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 · Configuration

Licensing

Activation end to end: where the install ID comes from, the two places a license can live and which one wins, what the grace window actually does, and how rebuilding an appliance affects your deployment count.

01

How licensing works

A TestVibe license is a signed text blob you paste into your own install. Nothing phones home — not at activation, not at run time, not ever.

Three facts that shape everything else

  • Offline and signed. The license is an ECDSA P-256 / SHA-256 signed token. Your install carries only the public key and verifies the signature locally. An air-gapped site licenses exactly like a connected one — the only thing that has to cross the gap is a 32-character ID going out and a block of text coming back.
  • Bound to one install. The signed claims carry your customer name and this install's install ID. A license issued for one install will not validate on another — the instance reports "This license was issued for a different install" and stays unlicensed.
  • Self-hosted only. Licensing is skipped entirely on TestVibe's cloud. The switch is TV_SELF_HOSTED; when it is not set explicitly the app infers self-hosted from TV_STORAGE_PROVIDER=http. Both the appliance and the Compose stack set TV_SELF_HOSTED=1 for you.

What a lapsed license actually stops

Enforcement happens at one chokepoint — the same pre-flight every dispatch already goes through. So it is precise, and it is not a kill switch.

StateWhat still worksWhat is blocked
Valid Everything. Nothing.
Grace Everything, plus an amber banner in the app that a user can dismiss for the session. Nothing.
Read-only Sign-in, browsing projects, reading features, opening past runs, traces and reports, exporting, deleting. A red banner that cannot be dismissed. New test runs, new AI generations, new load tests.
Nothing is deleted and nothing is hidden. Read-only pauses the three things that consume machine time. Your data stays exactly where it is and stays readable, and activating a license restores full service within seconds — see Install it.
02

Find the install ID

The install ID is generated by your own instance the first time it starts against its database, and it is stored there. It is not baked into the image, so two installs from the same download have different IDs.

Where it lives

A 32-character hexadecimal string in the single-row tv_instance table, written once on first boot and never re-generated. On the appliance that table is on the data disk, which is why the ID survives an upgrade that replaces the OS disk.

example
0303e70865794216b70ac562a227da18

Three ways to read it

  1. The administration console (recommended). Open the console — http://<ip>/admin or http://<ip>:5120 on the appliance, http://<host>:5120 on Docker Compose — sign in, and go to License. The install ID is shown on a copy line labelled "Install ID — send this to TestVibe to be issued a license", next to a status pill and, while unlicensed, a "N day(s) of grace left" note.
  2. Inside the application. Sign in as an owner or admin and open Settings → License. This panel exists only on self-hosted installs. It shows the same install ID with a copy button, the live verdict and its reason, a Re-check button, and a paste box.
  3. The log. The licensing service prints one line per check, always ending with the install ID.
    appliance
    sudo journalctl -u testvibe-web | grep '\[license\]' | tail -3
    docker compose
    docker compose logs web | grep '\[license\]' | tail -3
    expected output
    [license] WARN grace — No license is installed. Activate a license within
              13 day(s) to avoid interruption. (install 0303e70865794216b70ac562a227da18)
Kubernetes Same line, same place — it is the web pod's stdout: kubectl logs deploy/testvibe-web | grep '[license]'. The console is reachable through the chart's admin service on 5120 as well.
If the console says it cannot read the instance identity That is a database message, not a license message. The console reads tv_instance directly, so "Could not read this instance's identity" means the database is unreachable or the migrations have not run. Fix that first; there is no install ID to copy until the schema exists.
03

Issue a license

Licenses are minted on the customer portal, by you, against your own subscription. There is no ticket to open and no waiting.

  1. Open the portal and sign in with the email on your subscription — the same 6-digit-code sign-in you used for this page. If you are reading this, you are already signed in.
  2. Go to the License tab. The header states your entitlement: deployments used out of your quota, how many remain, your edition, and whether the subscription is perpetual or has an end date.
  3. Paste the install ID into the Install ID field. Add a Label — "Production", "DR site", "QA" — so the deployment list stays readable a year from now. The label is for you; it is not part of the signed claims.
  4. Press "Issue license". You get back a block of license text and a SHA-256 checksum of it. Copy the whole license text — it is one long token, and leading or trailing whitespace is trimmed for you on paste.
  5. Check the confirmation line. A first activation says "Licensed". Re-issuing for an install you already own says "Re-issued for an install you already own — this did not use another deployment." That distinction is the one worth reading.
The portal fails closed. If your account is not entitled, it refuses rather than issuing something that will not verify; if the signing key is unavailable it returns an error rather than an unsigned token. You will never be handed a license that quietly does not work.
04

Install it

Two routes, deliberately. Click-ops shops paste it into the console; infrastructure-as-code shops pin it in the environment and keep the box reproducible.

Route A — the console

The normal path, and the only one an appliance operator has, because an appliance has no OS login.

  1. Console → License → the Activate a license box.
  2. Paste the license text and press Activate. It is stored in the database, on the data disk, alongside the install ID.
  3. Watch the status pill flip to Valid. If it does not within a minute, see Troubleshooting.

A stored license can be removed again with Remove stored license — the panel also shows when it was last changed.

Route B — the environment

For Compose, Helm and anything else where the deployment file is the source of truth.

.env
TV_LICENSE=<the license text from the portal>

Or keep it out of docker inspect and out of your environment listing entirely by mounting it as a file:

.env
TV_LICENSE_FILE=/run/secrets/testvibe-license

Restart the web service after changing either one — the environment is read at process start.

How fast does it take effect?

Faster than the hourly re-check would suggest, because the paste path has its own signal. The application watches for the license row changing every 20 seconds and re-evaluates the moment it moves; the full re-check runs hourly on top of that.

  • Console paste: live within about 20 seconds. The app's header banner refreshes on its own poll, so it clears without a page reload.
  • Environment variable: at the next restart of the web service, because that is when the environment is read.
  • Impatient? Settings → License → Re-check in the app forces an immediate evaluation.
Why 20 seconds and not instant The console is a separate process from the application — a deliberate split, so the process that executes AI-generated test code does not share the process that holds your configuration. It writes the license to the database; the application notices on its next probe. Nothing is broken if the pill in one and the banner in the other disagree for a few seconds.
05

Which license wins

If a license exists in more than one place, exactly one of them is in effect, and the rule is the same rule every other setting on this install follows: the environment is the floor and cannot be overridden from a web page.

PrioritySourceWins over
1 TV_LICENSE_FILE — the contents of the file it points at, on the web service. Everything below.
2 TV_LICENSE — the environment variable on the web service. The pasted license.
3 The license pasted into the console and stored in the database. Nothing.
The failure this causes, and how the console warns you Pasting a license while TV_LICENSE is set does nothing visible. The paste is stored, and then ignored. The console detects this and prints a lock notice on the License panel — "TV_LICENSE is set in this instance's environment, which takes precedence over anything pasted here. Replace it where the environment is defined." If you see that notice, edit your .env / Helm values / testvibe.env instead and restart the web service.

The console can only inspect its own process environment. On both shipped shapes that is the same environment the application sees — the Compose stack shares one .env, and the appliance's console and web units both read /etc/testvibe/testvibe.env — so the notice is accurate. On a bespoke deployment where the two processes are configured separately, trust the web service's environment, not the notice.

06

Grace and read-only

You are never locked out of a fresh install. An unlicensed instance runs normally for a fixed window first — long enough to evaluate, not long enough to run a business on.

The grace clock GRACE — everything works · 14 days by default READ-ONLY — data readable, new work paused first seen unlicensed grace ends Activating a valid license at ANY point returns the instance to Valid and CLEARS the clock, so a later lapse starts a fresh full window rather than resuming a spent one.
Diagram — the grace window, and what activation does to it.

The rules, precisely

  • The clock starts when the instance first notices it is unlicensed — not when you downloaded the image, and not at midnight. The moment is written to the database, so restarting the box, reverting a snapshot of the OS disk or changing the system clock does not extend it.
  • The window is 14 days by default, set by TV_LICENSE_GRACE_DAYS. The console and the in-app banner both count down in whole days remaining.
  • Read-only pauses three things: new test runs, new generations, new load tests. That is the entire list. Reading, exporting and deleting are never gated — deleting in particular, because it is the recovery path for a full disk.
  • Activating clears the clock. The stored "first unlicensed" timestamp is wiped on a successful validation, so if the license later expires you get a full fresh grace window rather than an already-spent one.

Expiry

A perpetual license has no expiry date and simply stays valid. A term license carries one in its signed claims, and the console shows it as "Licensed to <you> until YYYY-MM-DD".

  • On the day it passes, the instance reports "The license expired on YYYY-MM-DD" and enters grace — a full 14-day window, because the clock was cleared while the license was valid.
  • Expiry is evaluated on the hourly cadence, not to the minute. A license does not stop working at a precise second.
  • Renew by issuing a new license for the same install ID on the portal. That is a re-issue, so it does not consume another deployment.
Set a calendar reminder, not a monitor. The instance does not email anyone when a license nears expiry, and nothing reaches out to us. The console's License panel and the in-app banner are the only notice you get.
07

Rebuilds and seats

This is the question that decides how freely you can operate the box, so it is worth being blunt about it: rebuilding does not cost you a deployment, as long as the data survives.

What consumes a deployment, and what does not

You do thisInstall IDDeployment count
Upgrade the appliance — replace the OS disk, keep the data disk Unchanged No change
Pull new container images, keep the Postgres volume Unchanged No change
Restore a database backup onto a new host Unchanged No change
Re-issue the license for an install ID you already activated Unchanged No change — free, explicitly
Start again on an empty database (new data disk, wiped volume, clean install) New Consumes one
The design intent The install ID is stored with your data, not with the software, precisely so that the operations you should feel free to perform — upgrade, patch, redeploy, restore — cost nothing. If rebuilding burned a seat, the two-disk appliance layout would be pointless.

Second environments

A staging or DR instance is a separate install with its own install ID, so it needs its own license and does consume a deployment. Check the portal's License tab for what your subscription allows before you build it — the header shows "N used / M" and how many remain.

A DR instance restored from the production database is not a second install — it carries the same install ID and the same license. Do not run both at once expecting them to be distinguishable; they are not.

Honest limit. Because the install ID lives in the database, binding prevents reuse across fresh installs, not across a cloned data volume. This is deliberate: no container fingerprint is both stable across upgrades and impossible to clone. Treat it as anti-casual-sharing, not as DRM — and please stay inside your deployment count.
08

Reference

Every licensing variable the application reads. All of them are environment-only — none of them appear in the console's settings catalog.

VariableDefaultWhat it does
TV_SELF_HOSTED 1 in both shipped stacks Marks this an on-prem install. Turns licensing on and credit gating / token metering off. If unset, the app infers self-hosted from TV_STORAGE_PROVIDER=http.
TV_LICENSE unset The signed license text. Beats anything pasted in the console.
TV_LICENSE_FILE unset A path to a file containing the license text — the Docker/Kubernetes secret-mount form. Beats TV_LICENSE.
TV_LICENSE_GRACE_DAYS 14 Length of the grace window in days, from when the instance first noticed it was unlicensed.
TV_LICENSE_CHECK_MINUTES 60 The full re-evaluation cadence — the one that rolls grace into read-only and notices an expiry.
TV_LICENSE_PROBE_SECONDS 20 How often the app checks whether the stored license changed, so a paste in the console takes effect in seconds rather than in an hour.

On the appliance these go in /etc/testvibe/testvibe.env, which both the web service and the console read. There is no OS login on the appliance, so in practice appliance operators use the console and leave this file alone.

09

Troubleshooting

The instance always tells you why it is unlicensed, in one sentence, in the console and in the log. Start there.

What it saysWhat it meansFix
"No license is installed." Nothing pasted, nothing in the environment. Follow Issue a license.
"The installed license is invalid or could not be verified." The signature did not check out — almost always a truncated or re-wrapped paste, or text pasted from a document that changed the characters. Copy it again from the portal with the copy button and paste as plain text. Compare the portal's SHA-256 checksum against what you pasted if you want certainty.
"This license was issued for a different install (…)." Right customer, wrong install ID — usually a license from a previous clean install, or from your other environment. Read the current install ID (section 02) and issue a license for that one. It is free if you already own the install.
"The license expired on YYYY-MM-DD." A term license passed its end date. Re-issue for the same install ID. Grace restarted at full length, so you have room.
Pill says Stored (not yet re-checked) The console saved the license but has not been able to ask the running application for its verdict. Wait ~20 seconds and reload, or use Settings → License → Re-check in the app.
Everything looks right, but the pill never turns green TV_LICENSE is pinned in the web service's environment and the paste is being ignored. See Which license wins.

Where to go next