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.

host.nixnix
{ 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.

forgejo-vm.nixnix
{
  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;
  };
}