The guide · explanation
Shared folders
A share block on a VM maps a host directory to a guest path. Two transports sit behind that one surface, virtiofs and SMB, and vmlab picks between them, serves them, and mounts them in the guest once it is ready. This chapter explains how each works, how the choice is made, and what a share is not. The fields are in Lab file: vm and its children.
vm "dev01" {
template = "x86_64/ubuntu-24.04"
nic { segment = "lan" }
share { host = "./src" guest = "/mnt/src" }
share { host = "~/datasets" guest = "/mnt/data" readonly = true }
}
On a Windows guest, guest is either a drive letter (guest = "S:", mapped with net use) or a folder on an existing drive (guest = "C:\data", a symbolic link to the share). Pick a free letter: on vmlab's Windows VMs D: is usually the optical drive, so guest = "D:\data" fails with "The device is not ready".
Two transports
virtiofs is the fast path. vmlab runs one virtiofsd per share and attaches it as a vhost-user-fs device, which the guest mounts natively with mount -t virtiofs. No guest network is involved and no credential exists. It needs a virtiofs client in the guest: Linux kernels from 5.4 have one, and a Windows guest needs the virtio-win driver and WinFsp baked into its template. A profile declares whether its guests mount virtiofs with the virtiofs capability flag, and the shipped linux-modern profile sets it.
Do not use virtiofs for large files on a Windows guest. virtio-win 0.1.302's VioFS driver fails large reads at random with `Error performing inpage operation, and robocopy /J` can report success for a copy that cannot be read back. The fault is in the guest driver, and no virtiofsd setting avoids it. VioFS 0.1.285 does not have it. vmlab cannot tell which driver a template carries, so the shipped Windows profiles leave the virtiofs flag off and their auto shares ride SMB. A share that rides virtiofs into a Windows guest anyway, through an explicit transport = "virtiofs" or a profile that turns the flag on, is still served, and vmlab up prints a warning naming it.
virtiofs used to be incompatible with online snapshots, because the FUSE session state lived outside QEMU. That objection expired with QEMU's device-state transfer: vmlab runs virtiofsd with migration mode enabled, so its session state, open handles included, rides the snapshot's migration stream. The cost is that a VM carrying a virtiofs share has its RAM moved to a shared memory backend, and one daemon per share.
Distributions ship older virtiofsd releases than vmlab's features need. Ubuntu 24.04 ships 1.10.0, for example. vmlab reads the binary's --help once and passes only the flags it lists, so any Rust virtiofsd serves a share. Two features depend on the release: - Online snapshots of a machine with virtiofs devices need 1.11.0 or later, the first release with --migration-mode. On an older one, vmlab snapshot create and the restore of an online snapshot refuse and name the release. Offline snapshots still work. - A read-only share needs 1.13.0 or later, the first release with --readonly. On an older one, an auto read-only share rides SMB, and a virtiofs read-only share fails vmlab validate. A binary that lacks the basic flags, such as QEMU's retired C virtiofsd, fails vmlab validate for every virtiofs share. The install script reports an old virtiofsd too. Set VMLAB_VIRTIOFSD to use a newer build than the distribution's.
SMB is the universal fallback. The lab daemon serves each share at the segment gateway as \\<gateway>\<share>, so a Windows guest needs nothing extra, a Linux guest needs only cifs-utils, and an XP-era guest can be served too: smb1 = true on a share enables the SMB1 dialect and the NTLMv1 authentication those guests require. It is off unless asked for, and harmless on an isolated lab segment. SMB carries no device state at all, so a restored VM's sessions are stale TCP that the guest's SMB client re-establishes transparently.
How the transport is chosen
Each share carries transport = "auto" | "virtiofs" | "smb", defaulting to auto. The choice is made once, before the lab starts, as the **share plan**: which shares ride virtiofs, which fall back to SMB, which segments need a gateway rule to reach the SMB server, and which host port it takes. Two host facts feed it, what the host's virtiofsd can serve and whether a localhost port is free, and one guest fact, the profile's capability flag.
- auto takes virtiofs only when the host can serve it *and* the guest can mount it. SMB is the fallback, never the preference. A read-only share counts as servable only on a virtiofsd with --readonly.
- virtiofs always rides virtiofs. A host with no virtiofsd errors when the machine starts rather than degrading silently, and a virtiofsd that cannot serve the share fails vmlab validate.
- smb always rides the bundled SMB server.
Container volumes join the same plan. They have no per-guest question, since the micro-VM's own guest always mounts virtiofs, so they ride virtiofs whenever the host has a virtiofsd and SMB otherwise. A container with a read-only volume rides SMB on a virtiofsd without --readonly, because its volumes all take one transport.
The bundled smbd
No mature embeddable SMB server exists in Rust, so vmlab runs Samba's smbd as an unprivileged process. Three things make that work without root. It listens on a localhost high port, which any user may bind, and the switch proxies the segment gateway's port 445 onto it. Every Samba state directory is relocated: persistent state under the lab's .vmlab/smb, and the directories smbd binds unix sockets in under a short per-lab directory in vmlab's runtime root, so a lab in a deep directory cannot push a socket path past its 108-byte limit. And each share is served with force user as the invoking user, so smbd never switches uid. Samba is a documented host package, and smb1 shares depend on the installed build retaining NT1 support, which vmlab checks.
vmlab mints per-lab SMB credentials automatically, and a share is mappable only with its owning VM's credential. That scopes shares to the VM that declared them even on a shared segment. Authenticated NTLMv2 with SMB signing is the baseline, because current Windows hardening rejects unauthenticated shares. None of this is visible to you: the credentials are plumbed by vmlab.
Mounting in the guest
Once a VM is ready, the lab daemon runs the share plan's mount steps through the agent. The steps are a value computed per guest OS before anything runs, so what a Windows guest will be told to do can be read without booting one. Each step is retried for five minutes, because a freshly booted Windows guest cannot run net use for its first few. A step still failing after that emits share.unmountable naming the share and the last error, and that share's remaining steps are skipped.
- Linux, virtiofs: mkdir -p <guest_path>, then mount -t virtiofs <tag> <guest_path>.
- Linux, SMB: mkdir -p <guest_path>, then mount -t cifs //<gateway>/<share> <guest_path> with the generated credential.
- Windows: the credential is stored once per lab with cmdkey. A drive-letter target such as S: is mapped with net use. A folder-path target becomes a directory symbolic link to the UNC path with mklink /D, because a junction cannot target a UNC path. Each up keeps a link that already points at the current gateway and replaces one that points at the same share through an old gateway, for example after the machine moved to a segment with another subnet. If the folder path holds anything else, such as a real folder, vmlab leaves it untouched and reports the share as share.unmountable.
The agent's mounts run as the agent identity, SYSTEM on Windows, and a drive letter or folder link is visible in every session while each logon authenticates separately. An interactive logon picks the credential up from a logon hook. A logon the agent mints is not interactive and cannot store a cmdkey credential, so the agent opens an SMB session to the gateway in every logon it mints instead, before spawning anything. Without that, a login running vmlab shell or vmlab exec would see the share and be unable to open it.
XP-era guests mount by screen
The agent does not target XP or 2003-era guests, so automatic mounting does not apply there. A provision script maps the share instead, by typing net use X: \\<gateway>\<share> /user:… /persistent:yes through the screen-driven API described in Screens, input and vision.
What a share is not
Share contents are outside every snapshot
A share's files are host state. A snapshot restore never rolls them back, on either transport, and a destroy never deletes them. If you want a directory rolled back with the VM, it must be on the VM's disk.
A share also needs a segment to reach the gateway on when it rides SMB, so a VM with an SMB share and no NIC is a validation error. A port-isolated NIC can still reach the gateway, so shares work on isolated ports by design. The segment needs no nat either: gateway:445 reaches the lab's SMB server on a segment without egress, which gains that and nothing else.
A share is a passthrough view of the host directory, and that is the wrong tool for a watched source tree: file-change notification does not cross virtiofs or SMB, on either guest family, and it fails silently. A dev machine's source is therefore a workspace, a guest-local copy kept in step by vmlab's syncer, which Dev machines and the workspace syncer explains. Use a share for datasets, build caches and artefacts, and a workspace for the code you edit.