Skip to content

Custom kernel and config

This entry covers how to build your NixOS VM using a custom/third-party copy of the Linux kernel and a completely custom kernel config (.config). Note that this is a niche use-case, if you just want to enable support for additional hardware, then consult the NixOS wiki for custom configuration.


Again - if you don't know exactly why you need to do this, you won't have a need for it. The generic NixOS kernel and configuration is fine for most!

The result of what we want to build is something like this flake.

Initializing the project

You can start a new project using a template rather than re-creating the entire flake VM configuration from scratch:

nix flake github:nix4noobs/starters#basicvm-22-11

Using a custom kernel

linuxKernel.manualConfig is a helper function in nixpkgs which builds a Linux kernel given the source, a config file and (optionally) some patches.

Note that the version and modDirVersion values are required and used as sanity-checks. version just matches the version of the kernel you are using - you can get the proper value by running make menuconfig yourself. modDirVersion is just the concatenation of the version and the LOCALVERSION value. LOCALVERSION can be set to disambiguate multiple installed kernels. In our case, the linux-qemu.config we provide defines CONFIG_LOCALVERSION=.nix4noobs.

Finally, note that src can be provided through other means, you can use a different helper to download a tarball, or provide a local source directory.

configuration.nix -- kernel configuration
# custom kernel, custom config
boot.kernelPackages = let
  my-kernel-pkg = { stdenv, lib, linuxKernel , ...} @ args:
    (linuxKernel.manualConfig rec {
      inherit stdenv lib;

      version = "6.3.0-rc4"; # from `make menuconfig`
      modDirVersion = "${version}.nix4noobs";  # "${version}{CONFIG_LOCALVERSION}"
      configfile = ./linux-qemu.config;
      allowImportFromDerivation = true;
      src = builtins.fetchGit {
        url = "";
        ref = "refs/tags/v6.3-rc4";
        rev = "197b6b60ae7bc51dd0814953c562833143b292aa";
      kernelPatches = [];
  my-kernel = pkgs.callPackage my-kernel-pkg {};
  pkgs.recurseIntoAttrs (pkgs.linuxPackagesFor my-kernel);


allowImportFromDerivation = true is important, it allows us to just specify the configFile value as a path which is then imported.


You may wonder where the stdenv, lib and linuxKernel attributes come from, seeing as my-kernel-pkg is called further down with an empty attribute set.

This is one of the magical properties of callPackage which, somehow, provides any attribute from nixpkgs/pkgs, if requested: See callPackage, a tool for the lazy.

Disabling modules check

If you built the VM as-is, you would likely run into failures toward the end of the build as the builder will attempt to copy various kernel modules from the compiled output. If any of these modules fail, the step will fail, causing the entire build to fail.

This check reflects the standard NixOS kernel configuration, which builds modules for a multitude of things such as raid0, raid1 and so on. These checks cause issues when building a highly stripped-down kernel, but we can disable them by modifying the makeModulesClosure function with an overlay:

configuration.nix -- overriding makeModulesClosure
nixpkgs.overlays = [
  # (for custom kernel)
  # avoid errors on missing modules such as md, raid0, raid1 etc.
  (final: super: {
    makeModulesClosure = x:
      super.makeModulesClosure ( x // { allowMissing = true ;});

Overlays are covered elsewhere, but suffice it to say that super represents the state prior to this overlay being applied, and final represents the final state after all overlays are applied, and the returned attribute set are the changes this overlay makes to nixpkgs.

In this case, we redefine makeModulesClosure, calling the original implementation, but merging the input attribute set with one where the key allowMissing is set to true. This effectively skips this check.