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
import <vmlab.wcl>
lab "demo" {
gui = true // lab-wide default: open a VNC viewer on each guest
// when `vmlab up` runs interactively (VM `gui`
// overrides; skipped when there is no display)
vm "box" {
template = "x86_64/linux-modern"
memory = 2GiB
nic { nat = true }
}
}
VMs always run headless; gui only decides whether the CLI opens a viewer for you. Closing the viewer merely disconnects — the VM keeps running, and you can reattach any time with vmlab console.
§ 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.
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
provision "scripts/setup.ws" { } // runs once dc01 is ready
}
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 }
}
on "vm.crashed" { run = "scripts/collect-dumps.ws" }
on "host.disk_low" { run = "scripts/alert.ws" }
}