Nix Guide
A guide on what nix is from a long time nix user
Nix
If anyone has spent some time with me, they know that I am a big Nix affectionado. I introduce Nix to all the projects that I work on, and my collaborators complain about me introducing arcane files that they don’t understand. There seems to be very scant information on what nix is. This will be my 5 minute attempt at explaining what Nix is for my friends.
Ecosystem
Nix basically refers to three separate entities, as far as I can conceptualize.
- Nix, the language
- Nix, the package manager(!)
- NixOS, the operating system.
Nix the package manager is the bread and butter of the nix ecosystem. It is sort-of-kind-of like other package managers that people are familiar with (apt, yum, dpkg, pacman, portage, etc.) that it serves programs. However, it is not limited to serving programs to a system, which most package managers are limited to.
The primary goal of nix (package manager) is reproducibility. (what works on my machine works on others) and attempts to solve that problem. I like to explain it to people like it’s uv
or npm
; but for everything and not just one language. To achieve this, Nix uses functional programming paradigm. Which means that programs are pre-set composition of functions. This also gives it the property of being a declarative paradigm, and this becomes very useful for NixOS. Also, packages are not the only thing that the nix package manager can serve.
Nix Language
Nix language is a JSON-like language that has a bunch of constructs. It is pretty easy to learn in syntax. The most crucial things to know about the nix language are;
- Lists
- Attrsets
- Functions (lambdas)
Nix is a functional programming language. It inherits a lot of concepts from it. Most people will be familiar with lambda functions, nix uses lambda calculus extensively. Actually, the nix logo is just six lambdas.
A valid file/snippet with nix code that produces an object is called a nix expression. All nix ecosystem tools basically want a valid nix expression (almost always attrsets) formatted in a certain way.
Lists
List are easy, they are an (ordered) list of objects separated by whitespace.
1
list = [ "apple" "orange" ];
Attrsets
Attribute sets are basically dictionaries.
1
myset = { key1 = "value1"; key2 = 2; };
Functions
Functions take one input and gives an output. For example, the function that takes in a value and returns an attrset would be;
1
myFunction = input: { key = input; };
They are applied by whitespaces.
1
2
a = myFunction "hello";
# a evaluates to the atrrset { key = "hello"; }
Functions of multiple variables are constructed as functions that return functions.
1
2
3
4
5
myFunction = input1: input2: {key1 = input1; key2 = input2;};
a = myFunction "Hello";
# a evaluates to the function input2: {key1 = "Hello"; key2 = input2;}
b = a "World"
# b evaluates to the attrset {key1 = "Hello"; key2 = "World";}
There is a shorthand for functions that take attrsets with either fixed or flexible keys.
1
2
3
4
5
6
7
8
myFunction = {key1, key2}: "${key1} ${key2}";
# myFunction {key1 = "Hello"; key2 = "World!";}; evaluates to "Hello World!"
# myFunction {key1 = "Hello"; key2 = "World!"; key3 = " "}; fails due to extra key in the argument
myFunction = {key1, key2, key3}: "${key1} ${key2}";
# myFunction {key1 = "Hello"; key2 = "World!";}; fails due to missing key in the argument
myFunction = {key3, ...}@inputs = "${inputs.key1}${key3}${inputs.key2}";
# myFunction {key1 = "Hello"; key2 = "World!"; key3 = " "}; evaluates to "Hello World!"
This is a very crass overview, but everything can be inferred easily if you are familiar with JSON and read a bit of nix code.
Nix the Package Manager
Nix package manager, the primary role is to pick up a specific attrset with certain keys (called a derivation) and builds a store object (or package) using the information there. How this is done is elaborated in Nix Pills and is not super important to cover. But all packages are placed in a directory called store. (It’s conveniently located at /nix/store
) Here is a bunch of packages with their hashes. Derivations are not built for the system tools, but if they use shared resources (other binaries, libraries etc.) they are linked from other store paths. This creates a reference tree structure in the store. closure of a package is the collection of all the folders that a package depends on. (All the downstream packages depend on.) Here comes the reproducibility, if you were to copy the closure of a package to another computer, you should 100% be able to run that binary there.
Nix package manager serves these packages.
Flakes
Flakes are an attrset that is one entry point to the nix package manager. It’s really not difficult to understand. The attrset has 3 keys associated with it;
- description (optional) (string)
- inputs (optional) (attrset)
- outputs (attset)
Description is just meta information. Inputs doesn’t have to be there but can include other flakes or non-flake files. The flake provides outputs that can be consumed in some shape and form by nix tools.
The special thing about a flake is that the inputs are hashed and pinned in a lockfile. Which means that, if you have access to someone else’s flake and lock file, you can exactly reproduce their environment. This is great for reproducibility because this is the feature that nix lacks natively. Nix points to a nixpkgs repo for it’s derivations, but it does not specify which commit it points to. So if the packages get updated in the same tag, which often happens, you get derivations with different hashes that depend on when you built the package. Flakes solve this problem, and is a convenient organization for nix features. While flakes are still considered experimental features, the community has adapted it to the point where you can’t really find non-flake nix code anymore.
NixOS
NixOS is the operating system built on top of Nix. It uses the derivations in Nix to build an environment where all generic linux userspace tooling is there. It also allows configuring these tools with the module system.
Module System
The module system basically builds the configuration of a linux system through an attrset with the following properties;
- imports (list) that contains path to other modules
- options (attrset) defines what config options are available
- config (attrset) actions according to what options are set.
The configuration happens through each module mutating the system predictably depending on which options are set to what. There is the reproducibility; the full system is fully configured through configuring the config.
Your OS config is going to be defining some options most likely. Your OS is a module then. The other modules usually have actions conditionally attached to whatever the options are set to. Generally users don’t really have actions in their NixOS module.
The imports array loads all modules in a way that merges all the lists and attrsets. If there is a clash, such as two modules set the same option, then you will know due to build errors.
Conclusion
This is a conceptual overview for NixOS and the nix ecosystem. I will be writing a post on how to practically apply this as well.