Home What is a NixOS Flake?
Post
Cancel

What is a NixOS Flake?

If you find it hard to understand what a NixOS flake is and why you might use one, here you might find some answers!


What is a Nix Flake?

The Nix Wiki’s description of a nix flake is a little hard to understand in my opinion.

The flakes experimental feature introduces a policy for managing dependencies between Nix expressions, it improves reproducibility, composability and usability in the Nix ecosystem. Although it’s still an experimental feature, flakes have been widely used by the Nix community.

A flake allows you to separate a nix expression from your primarily installed NixOS or Nix Package Management system. This can be used for both development of applications (instead of default.nix or shell.nix) as well as defining your system configuration (instead of configuration.nix) - anything that a nix expression can do.

The key separating feature is that you can define inputs. Rather than taking from your nix-channels or your system’s nixpkgs, you have to define them as part of your flake.nix. You specify a URL to the repository that you want to use for your nix derivation, and you can have as many inputs as you want.

When you build your flake.nix, the process will create a flake.lock. This is the same as any lock file from your favourite development package manager - this will lock any repositories you use to a specific revision (e.g. git commit hash). This ensures that whenever you build the derivation, you will get a completely reproducible output.

Why use a Nix Flake?

If you just want to install packages that are part of the main repository, then a nix flake isn’t necessarily for you. Here are a few reasons why you may want to use them:

  • If you are developing an application and you update your system, it will not effect your application’s development environment.
  • If you want a specific version of an application, you can pin the input separately from other inputs.
  • If a package isn’t in the main nixpkgs repository but someone else has made a nix derivation for it (Have a look at my Foundry post for an example).
  • If you use multiple systems or servers, you can ensure that the exact same version of applications and services are used across all.

I may update these as I think of more reasons why!

How do I make a Nix Flake?

Enable Flakes

You first need to enable them in your system configuration.

  • On NixOS you can add the following into your configuration.nix (this needs to be done even if you are using a flake for your system)
1
2
3
{ pkgs, ... }: {
    nix.settings.experimental-features = [ "nix-command" "flakes" ];
}
  • If you are using just the Nix Package Manager, you need to add this to your ~/.config/nix/nix.conf or /etc/nix/nix.conf
1
experimental-features = nix-command flakes

Creating a Flake

You need to create a flake.nix in the directory you wish to use one. This might be where your /etc/nixos/configuration.nix is if you are doing a system, or the base directory of an application source.

Inside a flake.nix you need to specify both inputs and outputs. The example below defines two inputs:

  • nixpkgs - your everyday friendly nixos packages
  • flake-utils - a set of useful utilities for using flakes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
    inputs = {
        nixpkgs.url = github:NixOS/nixpkgs/nixos-23.05;
        flake-utils.url = "github:numtide/flake-utils";
    };

    outputs = {
        { self, nixpkgs, flake-utils, ... }:
        flake-utils.lib.eachDefaultSystem (system:
            let
                pkgs = import nixpkgs {
                    system = system;
                    config.allowUnfree = true;
                };

                lib = pkgs.lib;

            in
            {
                # Define your nix outputs here
            });
    };
}

This is the basic form I use for all my flakes - whether I’m creating a set of packages, creating devshells or homeConfigurations (for home-manager).

I use flake-utils here to get past the verbosity of having to define an output for each type of system (e.g. x86_64-linux or aarch64-darwin). The only reason you might not want this is if you only want to specify your derivation for a specific set of system architectures. The eachDefaultSystem below specifies the system argument so you don’t have to!

The next part between let and in defines the variables used for your output derivation. In this case, I’m creating a version of pkgs and lib based on the system provided by flake-utils (and also allowing unfree applications - you can remove that if you don’t want!).

1
2
3
4
5
6
7
8
let
    pkgs = import nixpkgs {
        system = system;
        config.allowUnfree = true;
    };

    lib = pkgs.lib;
in

Defining Outputs

From here, you define your outputs. You can have more than one type. You can find a list of all of the outputs that you can use here (this doesn’t list homeConfigurations as home-manager is not officially part of NixOS).

The ones I’ve used are:

  • devShell or devShells: Allows you to create a shell environment with whatever setup you need - equivalant to using a shell.nix. You can enter the shell with nix develop.

  • package or packages: Define packages that can be used in another flake, or built with nix build (the output will be symlinked to the result folder).

  • nixosConfigurations: Create a system configuration similar to your /etc/nixos/configuration.nix.

The following shows a simple example of creating a devShell with a set of applications. This can be entered using nix develop.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
    inputs = {
        nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.05";
        flake-utils.url = "github:numtide/flake-utils";
    };

    outputs = { self, nixpkgs, flake-utils, ... }:
        flake-utils.lib.eachDefaultSystem (system:
        let
            pkgs = import nixpkgs {
                system = system;
                config = { allowUnfree = true; };
            };

            lib = pkgs.lib;

        in
        {
            devShell = pkgs.mkShell {
                buildInputs = with pkgs; [
                    ffmpeg5
                    python310
                    vscode
                ];
            };

        });

}

Examples on the Blog

This section will be expanded when I get more examples written up on the blog.

These posts still work on the latest version of NixOS as of the time of writing (23.05) and should do for quite some time yet.

Some Final Thoughts

Hopefully you now have a better understanding of what flakes are and why you might use them! I plan to add new posts with different examples in the future.

If you found this useful, then share this around to those that may find it useful! Please leave a comment down below if you have any questions or something else to say!