Published on

How to Convert Default NixOS to NixOS with Flakes

Authors

This post is part of a series about NixOS. The series is done in support of my upcoming Opus Magnum, "Practical NixOS: the Book", and you can read the detailed post here.

Should you find this article lacking some information, be sure to tell me in the comments or elsewhere, since it is a living document and I am very keen on expanding it.

This article's full code alongside the rest of configuration is available on GitHub.

Why Flakes?

If you don't want to know the backstory behind flakes, feel free to skip ahead to the hands-on part.

The first question regarding the flakes is most likely "why flakes?" Many articles explain what flakes are and why you should be using them, but often do it in a very confusing way, so let's clear it up!

You may have heard that flakes "are an experimental feature." Don't let that discourage you from using flakes! "Experimental" here simply means that the flakes API may change in the future, which could cause some of your previous builds to break. But even that should be rare. Flakes have been added somewhere in 2020, which is actually quite a while ago, and the API has been pretty stable since then.

First of all, let's discuss which problems flakes solve. Flakes are a step towards true reproducibility. The point is that the previous tools did not guarantee enough reproducibility. And what were those previous tools? Those were (and still are) the so-called nix channels. What are the channels though? From NixOS wiki:

Nixpkgs is the git repository containing all packages and NixOS modules/expressions. Installing packages directly from the master branch of the Nixpkgs repo is possible, but risky, since git commits are merged into master before being heavily tested. That's where channels are useful. A "channel" is a name for the latest "verified" git commits in Nixpkgs. Each channel has a different definition of what "verified" means. Each time a new git commit is verified, the channel declaring this verification gets updated. Contrary to a user of the git master branch, a channel user will benefit from both verified commits and binary packages from the binary cache. Channels are reified as git branches in the nixpkgs repository and as disk images in the channels webpage.

When you install NixOS from an ISO downloaded from official repositories, you are tied to NixOS channels by default. There are channels called "stable" (i.e. nixos-22.11) and unstable (nixos-unstable, nixpkgs-unstable). But there's nothing unstable about the unstable version, and there's nothing particularly stable about the stable versions either. These are simply references to the some commits in either of the branches of nixpkgs on GitHub.

By using channels, you have no power to choose which commit you want to use or to "lock" against that commit for future work. This is where the flakes come in handy. They allow you to do exactly that - specify, which commit you would like your system to be based on. Flakes also do more, of which I'll tell you about in later articles, but for now, let's do the highly anticipated - convert our NixOS system from default one to a flaked one.

Using Flakes

The following system is built upon the previous non-flaked version. If you're new to NixOS, I highly encourage you to read the previous article, NixOS for Apt/Yum Users. All the code, as usual, is available on GitHub in a self-sufficient form.

The good news is - you don't have to lose any of your previous configurations done in a "traditional" way. You would be able to reference those easily as a part of a flake.

Here's our minimal flake, adapted from the flake by m1cr0man1:

# /etc/nixos/flake.nix
{
  description = "flake for yourHostNameGoesHere";

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

  outputs = { self, nixpkgs }: {
    nixosConfigurations = {
      yourHostNameGoesHere = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          ./configuration.nix
        ];
      };
    };
  };
}

As you can see, it uses our ./configuration.nix as a module. What are modules again? Per NixOS wiki:

Modules are files combined by NixOS to produce the full system configuration. A module contains a Nix expression. It declares options for other modules to define (give a value). It processes them and defines options declared in other modules.

Simple as.

You may also see, that there's an "attribute" within the "outputs" attribute set called yourHostNameGoesHere: this is the name of the host you would be referencing when rebuilding your system. You may actually use any word you wish here, which may also differ from the actual host name (since the host name is defined within /etc/nixos/configuration.nix), but for the convenience, let's keep them the same. You may have multiple host definitions within the "outputs", since what you would actually be doing when rebuilding the system with flake is "realizing" one of the provided attributes within the "outputs" set. For examply, you may find it convenient to have one flake with definitions for multiple different hosts.

We also have to make a slight adjustment to our /etc/nixos/configuration.nix. Add the following snippet at the top of the file:

 nix = {
  package = pkgs.nixFlakes;
  extraOptions = ''
    experimental-features = nix-command flakes
  '';
};

And that's it!

To switch to your newly flaked-out system, simply type the following command as root/sudo (do not forget to switch the working directory to /etc/nixos):

nixos-rebuild --flake .#yourHostNameGoesHere switch

After some time, your system should have finished rebuilding itself, and is now a full fledged, flaked-out NixOS!

As you can see, you reference one of the outputs with a dot followed by a hash sign and then the output name.

After running the command, you should also find a new file called flake.lock, that should look something like this:

{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1676481215,
        "narHash": "sha256-afma/1RU0EePRyrBPcjBdOt+dV8z1bJH9dtpTN/WXmY=",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "28319deb5ab05458d9cd5c7d99e1a24ec2e8fc4b",
        "type": "github"
      },
      "original": {
        "owner": "NixOS",
        "ref": "nixos-unstable",
        "repo": "nixpkgs",
        "type": "github"
      }
    },
    "root": {
      "inputs": {
        "nixpkgs": "nixpkgs"
      }
    }
  },
  "root": "root",
  "version": 7
}

This file contains the information required to "pin" the specified flake inputs (in our case, only nixpkgs was specified as a flake input) at specific commits in some repositories (with GitHub by default, but other sources could be used as well).

Updating Flake

To update a flake (and thus, make use of fresher commits in your source repos), you would run nix flake update as root/sudo from within the directory where the flake resides (from /etc/nixos). This command will check the repos for the newer commits and rewrite the flake.lock to reflect the change. This command won't rebuild your system, so you would also have to run nixos-rebuild --flake .#yourHostNameGoesHere switch again.

Be sure to have your configuration checked in and tracked in git, especially the flake.lock, so in case you find yourself with a broken system after rebuild, or the system would simply refuse to build, you still have the option to rollback to a previous configuration. If you forgot to add flake.lock to git, you still can roll back to a previous configuration by selecting some proper generation from the bootloader (see the previous article), but you won't know (after running nix flake update), which exactly commits you have been referring to in your previous configuration.

Conclusion

Flakes are a somewhat recent addition to NixOS, which help you achieve true reproducibility for your builds. You should definitely be using flakes instead of channels, which flakes indent to supersede. Flaking-out your system is simple, and opens up countless possibilities, of which we have explored very few in our article, so stay tuned for more!

This article's full code alongside the rest of configuration is available on GitHub.

Footnotes

  1. https://gist.github.com/m1cr0man/8cae16037d6e779befa898bfefd36627

Made it this far? Enjoying the read?
Sign up for the newsletter to never miss a post