Reference · reference

Lab file: vm and its children

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

This chapter is the field-by-field reference for the vm {} block and every block it can carry. The blocks a vm shares with a container (login, nic, provision, playbook) are documented here once and referenced from container and its children. How a VM's hardware resolves is explained in the lab file guide; what the schema does not say is enforced by vmlab validate, and each entry below lists those rules.

vm {}

One virtual machine. The only required field is template; every hardware value not set here is inherited from the template's recorded hardware, then from the guest OS profile.

wcl
vm "<name>" {
  template    = "<arch>/<name>[@<version>]"   // or "scratch", or a registry ref
  arch        = "x86_64"
  profile     = "linux-modern"
  cpus        = 2
  memory      = 4GiB
  disk        = 64GiB          // scratch VMs only
  cdrom       = "./isos/boot.iso"
  floppy      = "./boot.img"
  depends_on  = ["dc01"]
  nested      = false
  gui         = false
  agent_update = true
  prevent_sleep = false
  display     = "virtio-vga"
  firmware    = "ovmf"
  tpm         = false
  secure_boot = false
  qemu_args   = []
  gpu       { … }
  nic       { … }
  disk "…"  { … }
  share     { … }
  media     { … }
  login "…" { … }
  provision "…" { … }
  playbook "…" { … }
}
FieldTypeDefaultMeaning
nameutf8 (label)requiredVM name, a DNS label, unique per lab; the inline block label.
templateutf8required<arch>/<name>[@<version>] from the store, scratch, or an OCI registry reference.
archutf8from templateArchitecture. Required for scratch and for registry references.
profileutf8from templateGuest OS profile supplying hardware defaults. Required for scratch.
cpusi64inheritedvCPU count, greater than 0. Inherited from template, then profile, if omitted.
memoryByteSizeinheritedRAM as a byte size, for example 8GiB or 512MiB. Inherited if omitted.
diskByteSizenonePrimary disk size, for example 64GiB. Scratch VMs only; rejected on cloned VMs.
cdromutf8nonePath to an ISO to attach as a CD-ROM, relative to the lab root.
floppyutf8nonePath to a floppy image to attach, relative to the lab root.
depends_onlist<utf8>noneVM or container names to wait for before this one starts. No cycles.
nestedboolfalseExpose hardware virtualisation (VMX/SVM) to the guest. Without it, an x86 guest under KVM has both masked. up warns when the host's KVM module has nested virtualisation off. No effect under TCG or on other architectures.
guiboollab guiOpen a VNC viewer on up. The VM always runs headless.
agent_updateboollab agent_update, else trueRefresh the guest agent on up and vm start when its version stamp differs from the agent this vmlab ships, and mark the VM diverged. false leaves the agent the template sealed. See vmlab up.
prevent_sleepboolfalseKeep the guest awake: the moment it suspends to RAM (ACPI S3), the lab daemon wakes it with QMP system_wakeup and records vm.woken with cause = prevent_sleep. Declared here only, never inherited: it is the daemon's policy, not hardware. The template build VM always has it on. Not accepted on a container {}. See vmlab status.
displayutf8inheritedQEMU display device string. Inherited from template, then profile.
firmwareutf8inheritedFirmware: ovmf or seabios. Inherited from template, then profile.
tpmboolinheritedEnable a TPM 2.0 device. Inherited from template, then profile.
secure_bootboolinheritedEnable secure boot; OVMF only. Inherited from template, then profile.
qemu_argslist<utf8>noneRaw QEMU flags appended last. The escape hatch; they win over everything vmlab generates.
gpu {}childnoneGPU acceleration: passthrough, virgl or vulkan.
nic {}childrennoneNetwork interfaces. No NICs means air-gapped. Shares need at least one.
disk {}childrennoneAdditional disks beyond the primary disk.
share {}childrennoneShared folders over virtiofs or SMB. SMB shares require at least one NIC.
media {}childrennoneISO or floppy images built from a folder.
login {}childrennoneIdentities a person's commands run as on this VM. Without one every verb keeps the agent identity.
provision {}childrennonewscript scripts run on vmlab up once this VM is ready, interleaved with its playbooks in declaration order.
playbook {}childrennoneconfig-weave playbooks applied on vmlab up, interleaved with its provisions in declaration order.

Template references

The template value takes one of three forms, and the form decides what else the block must say.

Known architectures are x86_64, x86, aarch64, riscv64, loongarch64, s390x and ppc64.

Validation

examples/mixed-lab/vmlab.wclwcl
vm "winsrv" {
  template = "x86_64/windows-server-2025"
  cpus = 4
  memory = 8GiB
  nic {
    segment = "lan"
    ip = "10.70.0.10"
  }  # DHCP reservation
  share {
    host = "./shared"
    guest = "S:"
  }  # auto-mounted when ready

  # Runs once winsrv is ready; nix01 depends on it, so it waits.
  provision "scripts/setup.ws" { }
}

The @dev decorator

@dev is written on the line before a vm or container block and marks it as a dev machine: when a workspace is named, vmlab syncs that directory onto it and keeps the two copies in step. It is a decorator rather than a child block because it states something about the machine; nothing it carries is a setting the guest sees. A bare @dev is complete. Any number of machines may carry it, and zero is normal.

wcl
@dev(default = true, workspace = "./workspace", workspace_guest = "C:\\src")
vm "dev01" { … }
ArgumentTypeDefaultMeaning
defaultboolfalseMake this the lab's default dev machine. At most one per lab. The only @dev machine in a lab is the default implicitly.
workspaceutf8noneHost directory whose contents sync into the workspace, relative to the lab root. Without it the machine carries @dev but has nothing to sync.
workspace_guestutf8profile, else /srcGuest path the workspace lands at. Inherited from the profile (C:\src on Windows profiles, /src on Linux ones) if omitted.

Unset arguments resolve in the order @dev argument, then the machine's effective profile, then the vmlab floor of /src. A profile that sets no workspace_guest still hosts a dev machine. The schema rejects an unknown argument, a wrong type, a repeated @dev, or @dev on a block that is not a vm or container. Validation adds one rule: two machines with default = true is an error naming both. With more than one @dev machine and none declaring default = true, the lab has no default; the selection ladder in vmlab dev then needs an argument. The dev-container example carries it on a container:

examples/dev-container/vmlab.wclwcl
@dev(default = true, workspace = "./workspace")
container "dev01" {
  image   = "alpine:3.22"
  profile = "container"
  // A dev machine builds things, and a container names its own size when
  // the profile's floor is not the right one.
  cpus    = 2
  memory  = 1GiB
  // `:idle` keeps the micro-VM up without running the image's entrypoint
  // — a dev container has no service to be.
  mode    = :idle
  nic { segment = "lan" }

  // The container identity floor (§19.2): the agent is root and root needs
  // no credential to become an account, so a Linux `login {}` may declare
  // the account alone. `elevated` is a validation error on this side.
  login "dev" { user = "dev" default = true }

  provision "scripts/dev-user.ws" { }
  provision "scripts/home-bits.ws" { }
}

login {}

A labelled identity on a machine: the guest account exec, shell and a script's as_login run as. See Logins. Repeatable, so one account may be declared twice at different elevation, and --user selects between labels.

wcl
login "<label>" {
  user     = "PROBE\\dev"
  password = "…"
  elevated = true
  default  = false
}
FieldTypeDefaultMeaning
labelutf8 (label)requiredIdentity label, what --user and as_login select it by, for example dev. Unique per machine; the inline block label.
userutf8requiredGuest account to log on as, for example PROBE\dev.
passwordutf8noneThe account's password, written plainly. Required on a Windows-family profile.
elevatedbooltrueRun the session elevated. Windows only; declaring it on a Linux-family profile is an error.
defaultboolimpliedMake this the machine's default identity. Implied when the machine declares exactly one login.

The family the rules are judged against is the machine's resolved profile: a name starting with windows is Windows, one starting with linux is Linux, and anything else, including custom and a registry template not yet pulled, is unknown and gets neither family rule. A container is always Linux. Validation enforces:

The secret is in the lab file

The password is written plainly because the lab's own provision script created the account with the same string. There is no credential store, no login verb and no wscript credential API.

nic {}

One network interface, attached to a declared segment or to the lab's built-in NAT segment. A machine with no nic {} blocks has no network hardware at all. See Networking.

wcl
nic {
  segment  = "corp"      // or: nat = true
  ip       = "10.50.0.10"
  gateway  = false
  mac      = "52:54:00:ab:cd:ef"
  isolated = false
}
FieldTypeDefaultMeaning
segmentutf8noneSegment name to attach to. Required unless nat = true.
natboolfalseShorthand: attach to the per-lab built-in NAT segment.
iputf8dynamicStatic IPv4, which becomes a DHCP reservation. Must be in the subnet and unique.
gatewayboolfalseMake this NIC the segment gateway. It must own the subnet's first usable address.
macutf8generatedFixed MAC, for example 52:54:00:ab:cd:ef. Generated and persisted otherwise.
isolatedboolfalsePort isolation: reach the gateway and forwards but not segment neighbours.

Validation enforces these rules:

disk {}

An additional disk beyond the primary one: blank at a given size, or a fresh FAT filesystem with a folder copied onto it, or both.

wcl
disk "data" { size = 10GiB }
disk "payload" { from = "./payload/" }
FieldTypeDefaultMeaning
nameutf8 (label)requiredDisk identifier; the inline block label.
sizeByteSizenoneBlank disk size, for example 10GiB. One of size or from is required.
fromutf8noneFolder copied onto a fresh FAT filesystem. One of size or from is required.

At least one of size and from must be set; both together is allowed. A from folder must exist under the lab root.

share {}

A host directory mounted into the guest, over virtiofs when host and guest support it, otherwise over SMB served at the segment gateway. See Shared folders.

wcl
share {
  host      = "./shared"
  guest     = "/mnt/src"
  readonly  = false
  smb1      = false
  name      = "src"
  transport = "auto"
}
FieldTypeDefaultMeaning
hostutf8requiredHost directory to share. Must exist.
guestutf8requiredGuest mount path, for example /mnt/src or D:\data.
readonlyboolfalseMount read-only.
smb1boolfalseEnable the SMB1 dialect and the auth relaxation XP and 2003-era guests need.
nameutf8derivedShare name. Derived from the guest path if omitted.
transportutf8autoauto picks virtiofs when host and guest support it, else SMB; virtiofs or smb force one.

The derived name joins the alphanumeric runs of the guest path with underscores, so /mnt/src becomes mnt_src and D:\data becomes d_data. Validation requires host to be a directory, a derivable or explicit name, and rejects smb1 = true with transport = "virtiofs". A VM whose shares are not all virtiofs needs a NIC.

media {}

An ISO or floppy image built from a folder and attached to the machine. Built images are content-addressed under the lab's .vmlab/ directory, so an unchanged folder is not rebuilt.

wcl
media { kind = "iso" from = "./unattend/" label = "UNATTEND" }
FieldTypeDefaultMeaning
kindutf8requiredImage kind: iso or floppy.
fromutf8requiredSource folder built into the image. Must exist.
labelutf8noneVolume label for the image.

Validation requires from to be a directory under the lab root.

gpu {}

GPU acceleration for the VM. At most one per VM. Passthrough hands a host device to the VM exclusively; virgl and vulkan render on the host GPU, which stays shared.

wcl
gpu { mode = "passthrough" address = "0000:01:00.0" }
FieldTypeDefaultMeaning
modeutf8requiredpassthrough, virgl or vulkan.
addressutf8noneHost PCI address, for example 0000:01:00.0. Required for passthrough.

Validation rejects passthrough without an address.

provision {}

A wscript provision script run on vmlab up once this machine is ready. It runs once, at its position among the machine's provision and playbook blocks. See Guest automation with wscript.

wcl
provision "scripts/setup.ws" { }
FieldTypeDefaultMeaning
scriptutf8 (label)requiredPath to the .ws file, relative to the lab root; the inline label. Must exist and compile.

Validation checks the file exists and compiles it, reporting compile errors against the block. Across machines, steps follow the order the machine blocks appear, with depends_on gating when each becomes eligible.

playbook {}

A config-weave playbook applied on vmlab up, interleaved with the machine's provisions in declaration order, and runnable on demand with vmlab playbook check and apply. See Playbooks.

wcl
playbook "playbooks/baseline" {
  play = "baseline"
  var "tz" { value = "UTC" }
}
FieldTypeDefaultMeaning
pathutf8 (label)requiredPlaybook folder containing playbook.wcl, relative to the lab root; the inline label.
playutf8requiredPlay name inside the playbook to run.
var {}childrennoneVariable overrides passed to config-weave for this machine's run.

Validation requires a non-empty play, a path that is a directory holding a playbook.wcl, and no variable set twice on one block. config-weave ships guest binaries for x86_64 only, so a playbook on a VM with another known arch is rejected.

var {}

One variable override for the enclosing playbook {}, passed to config-weave as --var name=value for this machine's run only.

wcl
var "tz" { value = "UTC" }
FieldTypeDefaultMeaning
nameutf8 (label)requiredVariable name; must be a WCL identifier. The inline block label.
valueutf8requiredValue, passed through verbatim. config-weave reads it as a WCL expression where it can (3 is an int, true a bool) and as a string otherwise.

Validation requires the name to be letters, digits and underscores, not starting with a digit, because config-weave binds each override as a let inside the guest.

on {}

An event handler, declared inside lab {}. The handler script runs when the named event fires; a failing handler is logged and never fatal. See Events and handlers and the event list.

wcl
on "vm.crashed" { run = "scripts/collect-dumps.ws" targets = ["dc01"] }
FieldTypeDefaultMeaning
eventutf8 (label)requiredEvent name to handle, for example vm.crashed; the inline block label.
runutf8requiredPath to the handler .ws file, relative to the lab root. Must exist and compile.
targetslist<utf8>noneVM or container names to restrict the handler to. Empty handles every occurrence.

Validation requires a known event name and a script that exists and compiles. targets may only be set on vm.*, container.* and snapshot.* events; a lab-wide event with targets is an error. A vm.* event may target only VMs and a container.* event only containers, and every target must exist.

examples/ad-lab/vmlab.wclwcl
on "vm.crashed"    { run = "scripts/collect-dumps.ws" }
on "host.disk_low" { run = "scripts/alert.ws" }