Multi-VM Networking

TestAnyware is built to run many VMs concurrently on the same host. Each VM gets its own id, its own spec file, and its own network address — there's nothing shared, no port conflicts, no global state.

The model

Every testanyware vm start produces:

  • a unique id (testanyware-<hex8>, or --id <custom>),
  • a per-VM spec at $XDG_STATE_HOME/testanyware/vms/<id>.json,
  • a per-VM backend clone (tart clone or QEMU copy),
  • VNC on a dynamically-assigned port,
  • the agent on port 8648 inside each VM (reached via the VM's own LAN IP).

All commands target exactly one VM at a time via --vm <id> (or TESTANYWARE_VM_ID in the current shell). There is no "default VM" — you must name the one you want.

Typical multi-VM workflow

# Start three VMs in parallel (each prints its own id)
vm1=$(testanyware vm start --platform macos)
vm2=$(testanyware vm start --platform linux)
vm3=$(testanyware vm start --platform windows)

# Drive them independently
testanyware --vm "$vm1" screenshot -o mac.png
testanyware --vm "$vm2" exec "uname -a"
testanyware --vm "$vm3" agent windows

# Tear all down
testanyware vm stop "$vm1"
testanyware vm stop "$vm2"
testanyware vm stop "$vm3"

Addressing a single VM from many shells

If you want a subshell or CI step to pick up the VM a sibling step started, export the id into the environment or write it to disk.

Pattern 1 — environment variable (single shell tree):

vmid=$(testanyware vm start)
export TESTANYWARE_VM_ID="$vmid"
# every child process sees TESTANYWARE_VM_ID and omits --vm
testanyware screenshot -o a.png
testanyware vm stop "$vmid"

Pattern 2 — per-operation handle file (CI / long-running scripts):

vmid=$(testanyware vm start)
printf '%s\n' "$vmid" > .testanyware-vmid

# later, from any fresh shell
testanyware screenshot --vm "$(cat .testanyware-vmid)" -o a.png

# teardown
testanyware vm stop "$(cat .testanyware-vmid)"
rm .testanyware-vmid

The filename is arbitrary; pick one that fits the operation and .gitignore it.

Recovering the id after a shell exit

The id is recoverable from disk even if the shell that captured it exits:

ls "${XDG_STATE_HOME:-$HOME/.local/state}/testanyware/vms/"*.json
# → /Users/you/.local/state/testanyware/vms/testanyware-a3f7b2c1.json

The filename (minus .json) is the id.

Port layout

No host ports are shared:

  • VNC ports are dynamically assigned per-VM (tart picks a free high port; QEMU is configured with -vnc 127.0.0.1:<auto>). The actual port is written into <id>.json → vnc.port.
  • Agent port is 8648 inside the VM. The host reaches it via <vm-ip>:8648, where <vm-ip> is allocated per VM by tart (192.168.64.<n>) or by the QEMU virtio-net setup.

Because the VNC port and agent host are per-VM and recorded in the spec, there are no collisions regardless of how many VMs are running.

Limits

There's no hard cap in TestAnyware itself — the limits are your host's RAM, CPU, and network stack. A rough guide on a 32 GB machine:

Platform RAM per VM Typical concurrent count
Linux (tart) ~2 GB 8-10
macOS (tart) ~4-6 GB 3-4
Windows (QEMU+swtpm) ~4-6 GB 2-3

For CI workloads, prefer Linux goldens where you have a choice — boot time and footprint are significantly smaller.