lab {} block
WCL block
Declares a lab: a set of VMs plus the virtual networks connecting them, in vmlab.wcl.
A lab is declared with a lab {} block in vmlab.wcl, located by walking up from the current directory (like git). Lab-local working data — linked clones, snapshots, built media, screenshots — lives in .vmlab/ beside it; gitignore that directory. Every WCL file in a lab starts with import <vmlab.wcl>.
§ 1Minimal lab
wcl
import <vmlab.wcl>
lab "demo" {
gui = true // lab-wide default: show each guest's screen in a
// QEMU window (VM `gui` overrides; silently headless
// when no display server is available)
vm "box" {
template = "x86_64/linux-modern"
memory = 2GiB
nic { nat = true }
}
}
Closing a gui window kills that VM (QEMU semantics) — it surfaces as vm.crashed. The VNC socket (vmlab console) is available either way.
§ 2Examples
§ 2.1A small multi-segment lab
Two segments (one with the DC as DNS, one zero-config), three VMs with static and dynamic leases, a scoped provision and crash handlers.
wcl
import <vmlab.wcl>
lab "ad-lab" {
segment "corp" {
subnet = "10.50.0.0/24"
dns { server = "10.50.0.10" } // AD owns DNS
route { dest = "10.60.0.0/24" via = "10.50.0.254" }
}
segment "dmz" { } // zero-config: auto subnet, daemon DHCP/DNS
vm "dc01" {
template = "x86_64/windows-server-2025"
cpus = 4 memory = 8GiB
nic { segment = "corp" ip = "10.50.0.10" } // static → DHCP reservation
}
vm "client01" {
template = "x86_64/windows-11@26100.1" // version pin
depends_on = ["dc01"] // boot ordering
nic { segment = "corp" } // dynamic lease
}
vm "buildbox" {
template = "x86_64/linux-modern"
nic { nat = true }
}
provision "scripts/setup.ws" { vms = ["dc01"] }
on "vm.crashed" { run = "scripts/collect-dumps.ws" }
on "host.disk_low" { run = "scripts/alert.ws" }
}