Back to all posts

Adding machinectl support to microvm.nix

I use microvm.nix on my homelab server to run lightweight NixOS VMs. I wanted two basic host-side operations to feel native: machinectl should see the VMs, and SSH into them should not require remembering custom targets.

That meant adding machined registration for microvm.nix VMs and adding SSH-over-VSOCK support with a microvm -s helper.

I sent this upstream as microvm.nix PR #453.

What I wanted from the host

This is the kind of flow I wanted:

machinectl list
MACHINE CLASS SERVICE OS VERSION ADDRESSES
build-runner vm microvm.nix - - -
staging-api vm microvm.nix - - -
machinectl status build-runner
machinectl terminate build-runner
microvm -s build-runner
Connecting to build-runner via VSOCK...
[build-runner]$

That sounds small, but it changes how the host feels. Instead of remembering unit names, helper scripts, or ad-hoc SSH targets, the VM becomes visible through the same systemd machinery that already manages it.

What microvm.nix needed

The SSH part is entirely in microvm.nix, not systemd. I added a guest option:

{
  microvm.vsock.cid = 4242;
  microvm.vsock.ssh.enable = true;
}

That enables sshd on VSOCK, so the host can connect without any guest network configuration. The host-side microvm -s command then targets either vsock/<CID> or the cloud-hypervisor socket mux, depending on the runner.

The machined part is separate. For that I added a registerWithMachined option, generated register/unregister scripts in the runner, and hooked them into the host units with ExecStartPost and ExecStopPost.

Why systemd 259 mattered

The old machined APIs are pretty rigid:

  • RegisterMachine
  • RegisterMachineWithNetwork

They are enough to make a VM show up in machinectl, but not enough to attach much structured VM-specific metadata.

systemd 259 added RegisterMachineEx() and CreateMachineEx(). That is the interesting part of the release for this feature: machined can now accept extensible property sets instead of only the old fixed argument lists.

So the implementation in microvm.nix does this at runtime:

  • use RegisterMachineEx on systemd 259+
  • fall back to RegisterMachineWithNetwork or RegisterMachine on older hosts

That keeps the feature usable on older systems while letting newer hosts store richer metadata.

What gets registered

On newer hosts, microvm.nix can register properties like:

  • Id
  • Service
  • Class
  • LeaderPID
  • RootDirectory
  • NetworkInterfaces
  • VSockCID
  • SSHAddress

The VSockCID and SSHAddress properties are the interesting ones here. They make the machined entry useful for VM access, not just VM visibility.

SSH needed some cleanup too

While I was in there, I fixed microvm -s argument handling so SSH options and remote commands are split properly.

These two should do different things:

microvm -s build-runner -- -l root -i ~/.ssh/microvm_ed25519
Connecting to build-runner via VSOCK...
microvm -s build-runner uname -a
Connecting to build-runner via VSOCK...
Linux build-runner 6.12.0 ...

The first forwards SSH options. The second runs a remote command. That sounds obvious, but the CLI did not separate those cases cleanly before.

The edge cases

The imperative microvm@<name>.service template now tolerates missing microvm-register and microvm-unregister scripts. Without that, the generic template could fail for VMs that do not enable machined registration.

I also cleaned up the machineId handling and docs, because the same identifier flows through machined registration, SMBIOS UUIDs, and optionally guest /etc/machine-id. That is exactly the sort of thing that becomes confusing later if the semantics stay fuzzy.

Tests

I added focused regression tests for the machined registration path, microvm -s argument splitting, and the imperative template case.

  • checks/machined.nix covers registration and verifies VSockCID / SSHAddress metadata where applicable
  • checks/microvm-command.nix checks microvm -s argument splitting
  • checks/imperative-template.nix covers the template-unit case without register scripts

Thanks

Thanks to the microvm.nix reviewers and maintainers for pushing this into better shape, especially @SuperSandro2000 and the earlier review comments around RegisterMachineEx, machineId, and the imperative-template edge cases.

Result

microvm.nix now gives me a much nicer host-side story: the VMs show up in machinectl, newer systemd versions can store useful VM metadata, and SSH over VSOCK feels like part of the tool instead of an extra trick I have to remember.

Comments