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 otherinputs
. - 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 packagesflake-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
ordevShells
: Allows you to create a shell environment with whatever setup you need - equivalant to using ashell.nix
. You can enter the shell withnix develop
.package
orpackages
: Define packages that can be used in another flake, or built withnix build
(the output will be symlinked to theresult
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!