microvm.nix VLAN Networking
I use NixOS and microvm.nix for my homelab server. My home network is set up with VLANs to segregate traffic including self-hosted services. For my homelab server, each virtual machine is configured to be on its appropriate VLAN.
The network router assigns a reserved IP address and hostname via the configured MAC address. This keeps all DNS and routing of the services on the router instead of in the nix configuration.
The configuration below sets up two VLANs and corresponding bridges. All are attached to the same ethernet NIC.
Note: the number prefixes are the priority order of what networkd brings up.
{ config, ... }: {
networking = {
hostName = "homelab";
domain = "haruska.com";
useNetworkd = true;
};
systemd.network = {
enable = true;
# Create VLAN devices
# media VLAN (tagged 40)
netdevs."10-vlan-media" = {
netdevConfig = {
Kind = "vlan";
Name = "vlan-media";
};
vlanConfig.Id = 40;
};
# "services" VLAN (tagged 50)
netdevs."10-vlan-services" = {
netdevConfig = {
Kind = "vlan";
Name = "vlan-services";
};
vlanConfig.Id = 50;
};
# Create Bridge devices
# media Bridge
netdevs."10-bridge-media".netdevConfig = {
Kind = "bridge";
Name = "br-media";
};
# services Bridge
netdevs."10-bridge-services".netdevConfig = {
Kind = "bridge";
Name = "br-services";
};
# the main network attached to ethernet card "eno1"
# this configures networking for the host via DHCP and attaches the VLAN
# traffic to the same NIC
networks."20-main-network" = {
matchConfig.Name = "eno1";
networkConfig = {
# Attach VLANs to Ethernet Card
VLAN = [
"vlan-media"
"vlan-services"
];
# Pull DHCP address for actual Ethernet card MAC
# Statically assigned in unifi network
DHCP = "ipv4";
};
};
# Join VLANs to Bridges
# "media" VLAN uses the "media" Bridge
networks."20-vlan-br-media" = {
matchConfig.Name = "vlan-media";
networkConfig.Bridge = "br-media";
};
# "services" VLAN uses the "services" Bridge
networks."20-vlan-br-services" = {
matchConfig.Name = "vlan-services";
networkConfig.Bridge = "br-services";
};
# Bring up the Bridge networks
networks."30-br-media".matchConfig.Name = "br-media";
networks."30-br-services".matchConfig.Name = "br-services";
# Allow VMs on to the VLANs via bridges
# network devices prefixed with "vm-media-" are added to the media bridge
networks."40-vm-br-media" = {
matchConfig.Name = "vm-media-*";
networkConfig.Bridge = "br-media";
};
# network devices prefixed with "vm-service-" are added to the services
# bridge
networks."40-vm-br-services" = {
matchConfig.Name = "vm-service-*";
networkConfig.Bridge = "br-services";
};
# optional: if running docker containers, do not manage the internal
# docker network
networks."39-vm-docker" = {
matchConfig.Name = "veth*";
linkConfig = {
Unmanaged = true;
};
};
};
}The microvm.nix virtual machine then needs to be assigned to the correct VLAN bridge. It also needs a consistent MAC address for the router to assign a DNS entry and IP address. This example sets up a “forgejo” VM on the “services” VLAN.
{
config,
inputs,
pkgs,
lib,
...
}:
let
hostName = "forgejo";
domainName = "haruska.com";
vlan = "services";
# returns an octet (00-FF) based on a consistent hash of the hostname
# used for networking mac addresses and virtual interface IDs
# position is zero offset
hashedHostOctet =
pos:
let
sha1Hash = builtins.hashString "sha1" hostName;
hexValue = builtins.substring (2 * pos) 2 sha1Hash;
in
hexValue;
in
{
microvm = {
interfaces = [
{
# virtual tap ethernet interface
type = "tap";
# prefix the interface ID with the correct VLAN
# use an octet for uniqueness across all VMs on host
id = "vm-${vlan}-${hashedHostOctet 0}";
# 52:54:00 is a prefix for Locally administered addresses (LAA)
# use an additional 3 octets for a consistent unique mac address on the
# network
mac = "52:54:00:${hashedHostOctet 1}:${hashedHostOctet 2}:${hashedHostOctet 3}";
}
];
};
# Set the hostname and domain for the VM internally
networking = {
hostName = hostName;
domain = domainName;
};
}