Install & first run

Installing TestVibe

From an empty hypervisor to a machine that is booting. Written against release 0.4.3; substitute your version where the filenames appear. Read it in order the first time — the two mistakes that cost a day (the second disk, and the port) are settled in the first ten minutes.

01

What you are installing

One deployment, six services, and nothing that calls home at run time.

Pick a shape

All three run the same application. Choose by what you already operate. They are not always at the same version: containers and the Helm chart ship with every release, while appliance images come from a separate build and can be a release or two behind. The portal's Download tab offers what exists for each shape.

ShapeWhat it isChoose it when
Appliance
VHDX / OVA
A bootable two-disk virtual machine running the whole stack under systemd. No Docker inside, and it contacts no registry. Hyper-V, VMware or VirtualBox — and the only option for an air-gapped site.
Docker Compose One Linux host with Docker Engine and Compose v2, pulling private images. You already run Docker and want the fastest evaluation.
Kubernetes A Helm chart, shipped in the same bundle and published as an OCI artifact. You already run a cluster and have somewhere to put a database.

The appliance is two disks, and both are mandatory

This is the most common first-install failure. Every TestVibe service waits on the data disk, so an appliance booted with only disk 1 comes up, answers on port 80, and then never starts the application.

Disk 1 — operating system 20 GB · dynamic Ubuntu 24.04 LTS, .NET, Node.js Playwright browsers + Google Chrome /opt/testvibe/* — the published app systemd units, nginx front door DISPOSABLE. An upgrade replaces this disk wholesale. Nothing of yours lives here. Disk 2 — data 60 GB · dynamic /var/lib/postgresql — the database /var/lib/testvibe/blob — artifacts, traces /etc/testvibe — secrets, install id, license This appliance's whole identity KEEP IT FOREVER. It survives every upgrade. It is also your backup unit.
Diagram — the two-disk model. Sizes are the shipped defaults; both disks are dynamically expanding, so they do not occupy their full size on your storage.
Do not skip Attach both disks before the first power-on. No import wizard will insist on it, and the symptom — a bare testvibe login: prompt and no application — looks exactly like a broken image.

There is no operating-system login. By design.

There is no account you can sign in as: the build account is deleted before the image is published and again on first boot, and the service account's shell is /usr/sbin/nologin. The testvibe login: prompt on the VM console is not a door you are meant to open. The username and password printed above it are for the web administration console.

Everything is done in a browser. If you find yourself trying to get a shell, what you want is almost certainly on the troubleshooting list instead.

02

System requirements

The appliance runs Google Chrome, a .NET web application, a Node.js browser host, a test runner and PostgreSQL on one machine. Size it like a small application server, not like a utility VM.

The virtual machine

ResourceMinimumRecommendedNotes
vCPU 4 8 Every concurrent AI generation drives a real browser. 8 vCPU if several people will run tests through the day.
RAM 8 GB 16 GB Hyper-V: turn Dynamic Memory off, or set its minimum to 8 GB. A VM that starts at 512 MB and grows will fail the first boot before the balloon catches up.
Disk 1 (OS) 20 GB, dynamic Shipped size. Replaced wholesale on upgrade.
Disk 2 (data) 60 GB, dynamic Mandatory. Grow it later if artifact retention demands it — never detach it.
Firmware UEFI The image is UEFI/GPT. Hyper-V Generation 2; VirtualBox needs its EFI switch turned on by hand.
Storage speed SSD-backed First boot creates the database schema: 2–4 minutes on SSD, up to 20 on slow or contended storage.
Network One adapter, DHCP Any adapter model. Bridged or an internal switch your desktop can reach — NAT alone will not do (see below).

Network and firewall

  • DHCP is required on first boot. There is no OS login, so there is no way to type in a static address. For a fixed address, make a DHCP reservation for the VM's MAC. The hostname is testvibe, which is usually enough to spot it in your lease table.
  • Reachable from your users' desktops. In VirtualBox that means a bridged or host-only adapter: with NAT only, your browser cannot reach the appliance and Playwright traces fail to load.
  • Allow inbound 80, 8080, 8090 and 5120 from the networks that need them — URLs and ports says who needs which.
  • Outbound: the appliance must reach the site under test, your mail relay, your identity provider and your AI endpoint. It needs nothing from us at run time — no registry, no licence server, no telemetry callback.
  • Air-gapped sites: the only thing that crosses the boundary is the licence text you paste in, plus the console's optional update check, which you can leave unreachable.

Docker Compose and Kubernetes

  • Compose: one Linux host with Docker Engine and Compose v2, and the same 4 vCPU / 8 GB floor.
  • Kubernetes: any conformant cluster. The chart's bundled PostgreSQL is a single replica with no backups and no failover — fine to evaluate on. Point it at a database you operate for anything real.
  • Both need a credential for our private image registry. The appliance does not.
03

Download and verify

Downloads are on the portal's Download tab and are gated by your subscription. Every release publishes a SHA256SUMS file covering the artifacts of that version.

What each release publishes

FileWhat it is
testvibe-0.4.3.vhdx.tar The Hyper-V appliance. A tar carrying both disks, each zstd-compressed: testvibe-0.4.3-os.vhdx.zst and testvibe-0.4.3-data.vhdx.zst.
testvibe-0.4.3.ova The same appliance for VMware and VirtualBox. Both disks are inside the OVA already.
SHA256SUMS Checksums for that version's artifacts.
Compose bundle The docker-compose.yml, the .env template and the Helm chart under kubernetes/.

A format a release does not publish shows as unavailable rather than as a dead link. The 0.4.0 and 0.4.1 appliance images were withdrawn — they shipped identical secrets in every copy — and the portal refuses to serve them.

Verify what you downloaded

On Windows, in PowerShell:

PowerShell
Get-FileHash -Algorithm SHA256 .\testvibe-0.4.3.vhdx.tar
Get-Content .\SHA256SUMS

On Linux or macOS:

shell
sha256sum -c SHA256SUMS --ignore-missing

A good download says OK once per file, and nothing else:

shell — verifying the 0.4.3 download
$ sha256sum -c SHA256SUMS
testvibe-0.4.3.ova: OK
testvibe-0.4.3.vhdx.tar: OK

Real output from verifying the published 0.4.3 artifacts. Any line ending in FAILED means the file is not the file we published — download it again rather than trying to import it.

Compare the whole hash, not the first few characters. A truncated download is the usual cause of an import that fails with an unhelpful error. These images are gigabytes, and a browser that gave up at 92% leaves a file that looks complete in Explorer.

Unpack the Hyper-V download

Two steps: untar, then decompress each disk. Windows 10 and 11 include tar. For .zst you need a zstd decompressor — the official Windows build is on the zstd project's GitHub releases page, and NanaZip and 7-Zip ZS handle it too.

PowerShell
tar -xf .\testvibe-0.4.3.vhdx.tar

zstd -d .\testvibe-0.4.3-os.vhdx.zst
zstd -d .\testvibe-0.4.3-data.vhdx.zst

dir testvibe-0.4.3-*.vhdx

You should see exactly two files: testvibe-0.4.3-os.vhdx and testvibe-0.4.3-data.vhdx. If you see one, stop and decompress the other. This is where the "only one disk attached" failure is created.

04

Hyper-V, step by step

Generation 2, both disks, Dynamic Memory off, and one Secure Boot setting that Hyper-V gets wrong for every Linux guest.

With the Hyper-V Manager wizard

  1. Put both .vhdx files where the VM will live — for example C:\VMs\TestVibe\. Hyper-V does not copy them; it uses them in place.
  2. New → Virtual Machine. Name it (for example TestVibe 0.4.3) and click Next.
  3. Specify Generation → Generation 2.
    • This is not reversible after the VM is created. Generation 1 is BIOS and the appliance is UEFI/GPT — a Gen 1 VM will not boot it.
  4. Assign Memory → 8192 MB (16384 if several people will use it), and clear the "Use Dynamic Memory for this virtual machine" checkbox.
    • If your policy requires Dynamic Memory, set its minimum to 8192 MB rather than leaving the default.
  5. Configure Networking → pick a virtual switch that hands out DHCP addresses and that your users' desktops can reach. An External switch is the usual answer.
  6. Connect Virtual Hard Disk → "Use an existing virtual hard disk" and select testvibe-0.4.3-os.vhdx. Finish the wizard. Do not start the VM yet.
  7. Open the VM's Settings and attach the second disk.
    • SCSI Controller → Hard Drive → Add → Virtual hard disk → Browse → testvibe-0.4.3-data.vhdx.
    • This step is mandatory. Everything on the appliance waits for this disk.
  8. Settings → Security → Secure Boot. Leave Secure Boot enabled but change the template to "Microsoft UEFI Certificate Authority".
    • Hyper-V's default template for a Generation 2 VM is "Microsoft Windows", which does not trust the Linux boot loader. If the VM stops at a boot failure screen, this is the setting — or clear the "Enable Secure Boot" checkbox entirely.
  9. Settings → Processor → 4 virtual processors (8 if you sized for it).
  10. Optional but recommended: add a COM port. Hyper-V Generation 2 VMs have none by default, and a serial console is the one thing that makes a support log possible if the appliance never reaches a browser. See Troubleshooting for the one-line PowerShell command.
  11. Start the VM, then Connect to it and watch the console. Go to First boot.

Or with PowerShell

Run as Administrator. Adjust the paths and the switch name.

PowerShell (Administrator)
$Name   = "TestVibe 0.4.3"
$Dir    = "C:\VMs\TestVibe"
$OsVhd  = "$Dir\testvibe-0.4.3-os.vhdx"
$DataVhd= "$Dir\testvibe-0.4.3-data.vhdx"
$Switch = "External"          # your virtual switch

# Generation 2 (UEFI) with the OS disk attached, 8 GB of static memory.
New-VM -Name $Name -Generation 2 -MemoryStartupBytes 8GB `
       -VHDPath $OsVhd -SwitchName $Switch -Path $Dir

# 4 vCPU, no Dynamic Memory.
Set-VMProcessor -VMName $Name -Count 4
Set-VMMemory    -VMName $Name -DynamicMemoryEnabled $false

# *** THE DATA DISK. Nothing starts without it. ***
Add-VMHardDiskDrive -VMName $Name -Path $DataVhd

# Secure Boot: Linux needs the UEFI CA template, not the Windows one.
Set-VMFirmware -VMName $Name -SecureBootTemplate MicrosoftUEFICertificateAuthority

# Optional: a COM port, so the boot log can be captured if anything goes wrong.
Set-VMComPort -VMName $Name -Number 1 -Path "\\.\pipe\testvibe-console"

Start-VM -Name $Name
Get-VMHardDiskDrive -VMName $Name | Format-Table ControllerType,ControllerLocation,Path
Sanity check before you power on. That last line must print two disks. One is the failure this guide exists to prevent.
05

VMware and VirtualBox (OVA)

The OVA carries both disks and its own hardware description. VMware reads the firmware setting from it; VirtualBox does not, and needs one switch flipped before the first power-on.

The import, both disks, and EFI turned on before the first boot — then the first-run console checklist. Plays from YouTube — nothing loads until you click. Open on YouTube ↗

VMware Workstation, Fusion, ESXi and vCenter

  1. Import the OVA. Workstation and Fusion: File → Open and select testvibe-0.4.3.ova. ESXi and vCenter: Deploy OVF Template.
  2. Read the import summary. The OVA's own description repeats the two rules that matter — EFI firmware and both disks — and the sizing.
  3. Check the hardware after import: 4 vCPU, 8 GB RAM, firmware EFI, and two hard disks. Raise CPU and memory here if you sized for 8/16.
  4. Attach the network adapter to a network with DHCP that your users can reach.
  5. Power on and open the VM console. Go to First boot.

VirtualBox — turn EFI on before the first boot

  1. File → Import Appliance → select testvibe-0.4.3.ova → Import. Both disks come in with it.
  2. Before starting it: Settings → System → Motherboard → tick "Enable EFI (special OSes only)".
    • The appliance is UEFI/GPT. VMware and ESXi read that from the OVA automatically; VirtualBox ignores it and imports the VM as BIOS, which stops at "No bootable medium found!" — a message that says nothing about firmware.
  3. Or do it from the command line:
    shell
    VBoxManage modifyvm "TestVibe-0.4.3" --firmware efi
  4. Settings → Network → Attached to: Bridged Adapter.
    • NAT alone is not enough: your browser has to reach the appliance directly, and run artifacts and Playwright traces are fetched by your browser from the appliance's object store. With NAT only, traces render as broken links.
  5. Settings → System → Processor: 4 CPUs, and Motherboard → Base Memory: 8192 MB if the import did not already.
  6. Start it and watch the console window. Go to First boot.
06

Docker Compose

One Linux host, the private images, and six values you must supply. There is no safe default for any of them.

A cold pull of the shipped bundle, ending on docker compose ps with every container healthy. Plays from YouTube — nothing loads until you click. Open on YouTube ↗
  1. Unpack the compose bundle and copy .env.example to .env.
  2. Generate and fill in the required values. The REQUIRED block of .env.example lists exactly these:
    shell
    openssl rand -hex 32      # TV_PG_PASSWORD
    openssl rand -hex 32      # TV_BLOBSERVER_SIGNING_KEY
    openssl rand -hex 32      # TV_BLOBSERVER_ADMIN_KEY
    openssl rand -hex 32      # TV_RUNNER_KEY
    openssl rand -base64 24   # TV_ADMIN_PASSWORD
    openssl rand -base64 32   # TV_BOOTSTRAP_TOKEN
    • The stack refuses to start without the first five.
    • Set TV_BOOTSTRAP_TOKEN now, before your first docker compose up. It is what lets you create the first user who can sign in (step 7). Leave it out and the application exposes no such endpoint at all.
  3. Set both public URLs in .env if anyone other than the Docker host itself will use this:
    • TESTVIBE_PUBLIC_BASE_URL — the app, published on port 3000.
    • TV_BLOBSERVER_PUBLIC_URL — the object store, published on port 8080. Traces are fetched from here by the user's browser; leave it at localhost and trace viewing only works on the Docker host.
  4. Sign in to the registry. The images are private. Without this the next step stops on denied or manifest unknown, neither of which says "you are not signed in":
    shell
    docker login testvibe.azurecr.io -u <token-name>
    Paste the registry token we issued you as the password. It is pull-only, scoped to your subscription, and revocable on its own. Once per host.
  5. Start it.
    shell
    docker compose up -d
    docker compose ps
    The order is enforced: database → schema job → object store → app. The app and object store should report healthy, and the schema job Exited (0).
  6. Open the admin console on port 5120 and sign in as admin with TV_ADMIN_PASSWORD, then work through the setup checklist. The app itself is on port 3000.
  7. Create the first application user in the console. Go to First user, fill in an email and a password (at least 8 characters, including a letter and a number) and press Create user.
    • You should see "Created … They can sign in now — no email verification needed." Then sign in at http://<host>:3000.
    • TV_ADMIN_PASSWORD does not work in the app. It is a console credential, not an account.
    • Do not look for a sign-up form in the app: self-hosted defaults to invite-only, so the card offers none. The full walkthrough →
  8. Configure Mail (optional). Console → Mail: SMTP relay host and From address are the required fields. Press Send test and wait for the message to arrive.
    • Nothing above needs it. You need it for password resets, run notifications and emailed invitations — the copyable invite link works without it.
    • .env.example and the compose file carry no TV_SMTP_* variables and the web service does not forward them, so adding them to .env has no effect. If you manage configuration as code, add them to the web service's own environment: block.
One value not to set TESTVIBE_SECRET_PROTECTION_KEY is deliberately absent from the list above. It encrypts stored project secrets, and the app generates one into its own database the first time it needs one. The app and the console both read it from there, so there is nothing to set and your database backup already contains it.

Set one only if you want the key to live outside the database — and know that it is permanent, not a password you rotate. Change or lose it and every stored project secret becomes undecryptable, with no recovery, and every user is signed out.
07

Kubernetes

The chart ships inside the compose bundle under kubernetes/ and is also published as an OCI artifact. It refuses to render without the values that have no safe default.

The Helm install including the two warnings it prints — the ingress host artifacts are fetched from, and the bundled Postgres. Plays from YouTube — nothing loads until you click. Open on YouTube ↗
  1. Create the namespace and the pull secret. Without a pull secret every pod sits in ImagePullBackOff:
    shell
    kubectl create namespace testvibe
    
    kubectl -n testvibe create secret docker-registry testvibe-registry \
      --docker-server=testvibe.azurecr.io \
      --docker-username=<token-name> \
      --docker-password=<token-password>
  2. Install the chart.
    shell
    helm registry login testvibe.azurecr.io
    
    helm install testvibe oci://testvibe.azurecr.io/charts/testvibe \
      --namespace testvibe \
      --version 0.4.7 \
      --set global.imagePullSecrets[0].name=testvibe-registry \
      --set secrets.pgPassword="$(openssl rand -hex 32)" \
      --set secrets.blobSigningKey="$(openssl rand -hex 32)" \
      --set secrets.blobAdminKey="$(openssl rand -hex 32)" \
      --set secrets.runnerKey="$(openssl rand -hex 32)" \
      --set secrets.adminPassword="$(openssl rand -base64 24)" \
      --set secrets.bootstrapToken="$(openssl rand -base64 32)"
    Those six are the whole minimum. The last one lets the console create the first user who can sign in (step 5); leave it out and the application exposes no such endpoint at all. secrets.protectionKey is deliberately not among them — leave it empty and the app generates its own encryption key into its database, exactly as in Compose.
    • A wait-for-schema init container holds each app Deployment until the schema is current. Watch it with kubectl -n testvibe rollout status deploy/testvibe-web.
    • Chart 0.4.6 alone refused to render without secrets.protectionKey; it was never published, and from 0.4.7 there is nothing to carry forward.
  3. Set the object store's public URL. Traces are fetched by the user's browser, which cannot reach an in-cluster address, so without this they render as broken links:
    • --set blobserver.publicUrl=https://artifacts.your.host, or
    • --set ingress.enabled=true --set ingress.host=testvibe.your.host
  4. Reach the console. It gets no ingress by default; publishing an administrative console should be a deliberate act:
    shell
    kubectl -n testvibe port-forward svc/testvibe-admin 5120:80
    then open http://localhost:5120 and sign in as admin with secrets.adminPassword.
  5. Create the first application user in the console. Go to First user, fill in an email and a password (at least 8 characters, including a letter and a number) and press Create user.
    • You should see "Created … They can sign in now — no email verification needed."
    • secrets.adminPassword is not an application account, and the app shows no sign-up form: self-hosted defaults to invite-only. The full walkthrough →
  6. Configure Mail (optional). The chart carries no SMTP values — mail is console configuration, stored in the database, and no --set for it exists. Console → Mail, then Send test.
    • To pin the values as code instead, use web.extraEnv; the console then shows them locked.
  7. Activate the license and finish the checklist.
For production, bring your own database. The bundled PostgreSQL is a single replica with a PVC and no backups, no failover and no point-in-time recovery. --set postgresql.enabled=false --set postgresql.external.host=… — nothing else in the chart changes.

You can also supply the secrets from a Secret you manage yourself (secrets.existingSecret) — with an external secrets operator, sealed-secrets, or by hand. The Secret holding the protection key is annotated to survive helm uninstall, deliberately.