The guide · explanation

Guest OS profiles

6 min read · 2026-09-02 · vmlab 0.9

A profile is a named bundle of known-good defaults for one family of guest operating system: which QEMU machine type, which firmware, whether there is a TPM, which disk bus and NIC model the guest has drivers for, how scripted input reaches it, and where a dev workspace lands. Profiles are data, not code: they ship as WCL, and you can override or extend them from your own config directory. This chapter explains what a profile decides, the shipped set, how a user profile replaces a shipped one, and where a profile sits in the resolution chain. Every field is in Guest OS profile files.

What a profile decides

FieldDecides
machineThe QEMU machine type: q35 or pc (i440fx).
firmware, secure_boot, tpmOVMF or SeaBIOS, secure boot under OVMF, and an swtpm 2.0 device.
disk_bus, nic_model, displayThe devices the guest can drive: virtio, ide or sata disks; a NIC model such as virtio-net-pci, e1000 or pcnet; a display device such as virtio-vga, std or cirrus-vga.
cpus, memoryThe hardware floor a VM or container inherits when neither its block nor its template says.
agent_transportThe device the guest agent's channel rides: virtio-serial (default), isa-serial for a guest with no virtio drivers, where the legacy agent speaks over COM1, or none for a guest nothing can run an agent on. The older agent_channel bool still loads as an alias.
input_transportHow send_keys and the mouse reach the guest: qmp (default) or vnc. See Screens, input and vision.
virtiofsWhether the guest mounts virtiofs natively, which makes it a candidate for transport = "auto" shares. See Shared folders.
workspace_guestThe guest path an @dev workspace lands at when the decorator names none. See Dev machines and the workspace syncer.

The profile also classifies the guest as Windows-family or Linux-family, which is what the login {} validation rules and the Windows preconditions of the workspace syncer key on. A profile does not install the agent; that happens once, at template build, when the build stages the agent binaries on the bootstrap ISO and the template's unattended-install hook runs the install script. See Templates and the store.

The shipped set

ProfileMachineFirmwareTPMDevicesWorkspace
windows-11q35OVMF, secure bootyesvirtio disk and NIC, virtio-vgaC:\src
windows-10q35OVMFnovirtio disk and NIC, virtio-vgaC:\src
windows-serverq35OVMFyesvirtio disk and NIC, virtio-vgaC:\src
windows-legacypcSeaBIOSnoIDE disk, e1000 NIC, std VGA, for Vista/7/2008-era guests; virtio-serial agentC:\src
windows-xppcSeaBIOSnoas windows-legacy, with the agent on COM1 (isa-serial): NT4 through XP/2003C:\src
windows-9xpcSeaBIOSnoIDE disk, PCnet NIC, Cirrus VGA, VNC input, agent on COM1, 1 CPU and 256 MiBC:\src
templeospcSeaBIOSnoIDE disk, std VGA, no network by design, agent on COM1
linux-modernq35OVMFnovirtio everything, native virtiofs/src
linux-genericq35SeaBIOSnovirtio disk and NIC, std VGA, conservative elsewhere/src
containersize only: 1 CPU and 256 MiB, the micro-VM floor/src
customnothing assumed; supply everything on the VM or template and through qemu_argsfloor

linux-modern requests a VGA-compatible virtio GPU on x86 and downgrades to virtio-gpu-pci automatically on the non-x86 virt machine, which has no legacy VGA. The container profile carries only a size because a container micro-VM boots vmlab's own guest asset and has no firmware, disk bus or display to choose; it is deliberately an order of magnitude below the VM floor, and a container raises it with its own memory. custom sets nothing at all, so QEMU's defaults apply to whatever the VM and template leave unset. The shipped file is src/profiles/shipped.wcl in the repository.

windows-xp and windows-9x name agent_transport = "isa-serial": their guests have no virtio drivers, so the agent channel is a 16550 on COM1 and the guest runs the legacy agent, a small C program that speaks the same protocol and serves exec only. vmlab exec, readiness and the stop ladder work on such a guest; a terminal and vmlab cp refuse by name. On DOS the agent is the foreground program, and a command's output arrives after it exits. The bootstrap ISO carries the legacy builds with an install script each, so a template's own install hook, or a provision typing D:\INSTALL.BAT, puts it in place. See Templates and the store.

The templeos profile is the same bargain in another language. Its agent is HolyC, compiled by the guest, and a command is HolyC source rather than a program name, as in vmlab exec temple -- 'Dir;'. TempleOS reads no ISO 9660 and has no network, so the agent cannot ride the bootstrap ISO; a provision types it in with vmlab::templeos_agent_script(), which also registers it to start at every boot. The output is what the command printed, with the DolDoc markup reduced to the text the screen shows; a compile error or an uncaught exception exits 1 with the compiler's or the OS's report as the output.

Overriding with user profiles

vmlab loads the shipped set, then every *.wcl file in ~/.config/vmlab/profiles/, in sorted filename order. Each file starts with import <vmlab-profile.wcl> and contains profile "<name>" { … } blocks. A user profile whose name matches a shipped one replaces it entirely, field by field from scratch, rather than merging: a file that redefines windows-11 with only machine = "pc" produces a windows-11 with no firmware, no TPM and no device choices. Copy the shipped block and edit it. A new name extends the set and is usable from any lab's profile =.

~/.config/vmlab/profiles/mine.wclwcl
import <vmlab-profile.wcl>

profile "freebsd" {
  description = "FreeBSD: q35, SeaBIOS, virtio disk and NIC"
  machine     = "q35"
  firmware    = "seabios"
  disk_bus    = "virtio"
  nic_model   = "virtio-net-pci"
  display     = "std"
  cpus        = 2
  memory      = 2GiB
  workspace_guest = "/usr/src"
}

A profile file is validated with the same wording as a lab file: an unknown field, a value outside its set such as machine = "vax", a cpus below 1 or a missing import are all reported at their line, and every mistake in a file is reported in one pass. A lab naming a profile that does not exist fails vmlab validate.

Change the container floor for every lab

Dropping your own container profile into the profiles directory raises the micro-VM defaults for every lab on this host, which is simpler than adding memory = … to each container block.

Where a profile sits in the chain

Hardware resolves VM block, then template, then profile. A value set on the vm {} wins; a value not set there comes from the hardware the template recorded when it was built; a value not set there comes from the profile, and the profile's defaults are the floor. The profile itself is usually inherited from the template rather than named on the VM, and it is required on a scratch VM, which has no template layer. A template build resolves the build VM's hardware the same way, block over source template, with the template's own profile supplying the floor. secure_boot = true on a VM whose firmware resolves to SeaBIOS is a validation error, and because either value may have been inherited, the message names the layer each came from.

Two things a profile supplies resolve through their own chains, not the hardware one. A dev machine's workspace_guest resolves @dev argument, then profile, then the /src floor, and a profile with no dev keys still hosts a dev machine. A container's cpus and memory come from its block or its profile, and one of the two must supply each; there is no template layer and no vmlab floor, so a container naming no profile must set both.