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.
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.
| Shape | What it is | Choose 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.
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.
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
| Resource | Minimum | Recommended | Notes |
|---|---|---|---|
| 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.
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
| File | What 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:
Get-FileHash -Algorithm SHA256 .\testvibe-0.4.3.vhdx.tar
Get-Content .\SHA256SUMS
On Linux or macOS:
sha256sum -c SHA256SUMS --ignore-missing
A good download says OK once per file, and nothing else:
$ 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.
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.
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.
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
- Put both
.vhdxfiles where the VM will live — for exampleC:\VMs\TestVibe\. Hyper-V does not copy them; it uses them in place. - New → Virtual Machine. Name it (for example
TestVibe 0.4.3) and click Next. - 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.
- 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.
- 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.
- 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. - 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.
- SCSI Controller → Hard Drive → Add → Virtual hard disk → Browse →
- 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.
- Settings → Processor → 4 virtual processors (8 if you sized for it).
- 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.
- 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.
$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
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.
VMware Workstation, Fusion, ESXi and vCenter
- Import the OVA. Workstation and Fusion:
File → Open and select
testvibe-0.4.3.ova. ESXi and vCenter: Deploy OVF Template. - Read the import summary. The OVA's own description repeats the two rules that matter — EFI firmware and both disks — and the sizing.
- 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.
- Attach the network adapter to a network with DHCP that your users can reach.
- Power on and open the VM console. Go to First boot.
VirtualBox — turn EFI on before the first boot
- File → Import Appliance → select
testvibe-0.4.3.ova→ Import. Both disks come in with it. - 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.
- Or do it from the command line:
shell
VBoxManage modifyvm "TestVibe-0.4.3" --firmware efi - 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.
- Settings → System → Processor: 4 CPUs, and Motherboard → Base Memory: 8192 MB if the import did not already.
- Start it and watch the console window. Go to First boot.
Docker Compose
One Linux host, the private images, and six values you must supply. There is no safe default for any of them.
docker compose ps with every container healthy. Plays from YouTube — nothing loads until you click.
Open on YouTube ↗- Unpack the compose bundle and copy
.env.exampleto.env. - Generate and fill in the required values. The
REQUIREDblock of.env.examplelists exactly these:shellopenssl 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_TOKENnow, before your firstdocker 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.
- Set both public URLs in
.envif 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 atlocalhostand trace viewing only works on the Docker host.
- Sign in to the registry. The images are private. Without this
the next step stops on
deniedormanifest unknown, neither of which says "you are not signed in":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.shelldocker login testvibe.azurecr.io -u <token-name>
- Start it.
The order is enforced: database → schema job → object store → app. The app and object store should reportshell
docker compose up -d docker compose ps
healthy, and the schema jobExited (0). - Open the admin console on port
5120and sign in asadminwithTV_ADMIN_PASSWORD, then work through the setup checklist. The app itself is on port3000. - 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_PASSWORDdoes 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 →
- You should see "Created … They can sign in now — no email
verification needed." Then sign in at
- 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.exampleand the compose file carry noTV_SMTP_*variables and the web service does not forward them, so adding them to.envhas no effect. If you manage configuration as code, add them to thewebservice's ownenvironment:block.
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.
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.
- Create the namespace and the pull secret. Without a pull secret
every pod sits in
ImagePullBackOff:shellkubectl 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>
- Install the chart.
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.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)"secrets.protectionKeyis 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-schemainit container holds each app Deployment until the schema is current. Watch it withkubectl -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.
- A
- 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
- Reach the console. It gets no ingress by default; publishing an
administrative console should be a deliberate act:
then openshell
kubectl -n testvibe port-forward svc/testvibe-admin 5120:80
http://localhost:5120and sign in asadminwithsecrets.adminPassword. - 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.adminPasswordis not an application account, and the app shows no sign-up form: self-hosted defaults to invite-only. The full walkthrough →
- Configure Mail (optional). The chart carries no SMTP
values — mail is console configuration, stored in the database, and no
--setfor it exists. Console → Mail, then Send test.- To pin the values as code instead, use
web.extraEnv; the console then shows them locked.
- To pin the values as code instead, use
- Activate the license and finish the checklist.
--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.