Skip to content

Instantly share code, notes, and snippets.

@FlakM
Last active February 21, 2026 16:02
Show Gist options
  • Select an option

  • Save FlakM/0535b8aa7efec56906c5ab5e32580adf to your computer and use it in GitHub Desktop.

Select an option

Save FlakM/0535b8aa7efec56906c5ab5e32580adf to your computer and use it in GitHub Desktop.

Setting up qemu VM using nix flakes

Did you know that it is rather easy to setup a VM to test your NixOs configuration?

Create simple flake:

# flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";

  outputs = { self, nixpkgs, ... }:
    let
      system = "x86_64-linux";
    in
    {
      # test is a hostname for our machine
      nixosConfigurations.test = nixpkgs.lib.nixosSystem {
        inherit system;
        modules = [
          ./configuration.nix
        ];
      };
    };
}

Add your configuration

# configuration.nix
{ config, lib, pkgs, ... }: {
  # customize kernel version
  boot.kernelPackages = pkgs.linuxPackages_5_15;
  
  users.groups.admin = {};
  users.users = {
    admin = {
      isNormalUser = true;
      extraGroups = [ "wheel" ];
      password = "admin";
      group = "admin";
    };
  };

  virtualisation.vmVariant = {
    # following configuration is added only when building VM with build-vm
    virtualisation = {
      memorySize = 2048; # Use 2048MiB memory.
      cores = 3;
      graphics = false;
    };
  };

  services.openssh = {
    enable = true;
    settings.PasswordAuthentication = true;
  };

  networking.firewall.allowedTCPPorts = [ 22 ];
  environment.systemPackages = with pkgs; [
    htop
  ];

  system.stateVersion = "23.05";
}

Run a VM

git init # skip this step if you are inside already tracked repository
git add . # flakes requires at least tracking the files
nixos-rebuild build-vm --flake .#test
# expose port 22 from guest
export QEMU_NET_OPTS="hostfwd=tcp::2221-:22"
result/bin/run-nixos-vm

Profit!

# ssh onto the machine
ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no admin@localhost -p 2221
@sanzoghenzo
Copy link

sanzoghenzo commented Oct 29, 2024

Thanks @eljamm for the exaustive explanation!

program = "${inputs.self.nixosConfigurations.${name}.config.system.build.vm}/bin/run-nixos-vm";
inherit (import ./utils.nix { inherit inputs system; }) mkAppVM mkSystem;

Not what I'd call "cleaner" and "more readable" if you ask me 😅
Of course this is me trying to leverage the wonders of NixOS (declarativeness, reproducibility) but being too lazy to learn yet another programming language 🤣

But seriously, thanks again, I'll make treasure of your lesson!

@yaikohi
Copy link

yaikohi commented Dec 17, 2024

I just wanted to leave a comment that this gist helped me a lot so I wanted to thank you for writing this, the comments on here are also very insightful and gives me plenty to dive in.

Thank you all!!

@sikmir
Copy link

sikmir commented Mar 27, 2025

To use without nixos-rebuild:

nix build .#nixosConfigurations.test.config.system.build.vm

Or just nix run .#nixosConfigurations.test.config.system.build.vm

@Peritia-System
Copy link

Hate to Necropost but huge thanks to all of you! This was exactly what i was searching for.

May you always have the right amount of milk for your cereal and coffee :)

@andi242
Copy link

andi242 commented Oct 28, 2025

thank you guys, this helped me a lot!

Note: The rec keyword allows us to refer to vm-test from within the same attrset

this was the best explanation about rec I ever read! not overcomplicated, to the point. 😄

I found that the VM executable is named after the hostname. so if the hostname is different from the config, like setting it explicitly in config.networking.hostName this should work:

  mkVM = name: {
    type = "app";
    program = "${inputs.self.nixosConfigurations.${name}.config.system.build.vm}/bin/run-${
        inputs.self.nixosConfigurations.${name}.config.networking.hostName
      }-vm";
  };

(linebreaks don't matter in the .nix file, but better for readability)

@bodby
Copy link

bodby commented Jan 2, 2026

You could also use lib.getExe instead of typing out self.nixosConfigurations.${name}... twice and lib.genAttrs instead of flake-utils (see nixpkgs' flake.nix).

Mostly pet peeves, since these functions do exactly what everything else before does.

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

  outputs =
    inputs@{
      self,
      nixpkgs,
      ...
    }:
    let
      inherit (nixpkgs) lib;
      forSystems = lib.genAttrs lib.systems.flakeExposed;

      mkVM = name: {
        type = "app";
        program = lib.getExe self.nixosConfigurations.${name}.config.system.build.vm;
      };
    in
    {
      nixosConfigurations.host1 = lib.nixosSystem {
        # You don't need to specify `system` here, since the `nixpkgs.hostPlatform` option does already.
        specialArgs = { inherit inputs; };
        modules = [ ./hosts/host1 ];
      };

      apps = forSystems (system: {
        default = self.apps.${system}.vm-host1;
        vm-host1 = mkVM "host1";
      });
    };
}

@GamerBene19
Copy link

GamerBene19 commented Feb 21, 2026

Does anyone know how which config to add to pass through an usb device?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment