Let's get started with Nix! This article guides you into setting up the Nix package manager, along with flakes, and demonstrates some cool things it can do.
For a quick intro to what Nix is, check out this article I posted, which introduces Nix, and this series of articles to you. This article demonstrates an overview of the Nix package manager, more Nix concepts will be covered in future articles of this series.
Installing Nix
The Nix package manager can be installed on both Linux and Mac, and is also available as a Docker image for you to try out without installing it on bare metal. Windows users will have to use WSL2 to install Nix on their systems (if they're not using Docker).
The Nix website's download page guides you into using their installer to install Nix on your system. However, this article will use [Determinate Systems' Nix installer] instead, since it lets you easily undo all changes their Nix installer makes (i.e. uninstall) with one command, and it also enables Nix flakes by default, which you'd have to enable on your own if you were using the official Nix installer instead.
Their guide, Zero-to-Nix has detailed installation instructions using the Determinate installer, but in essence, it just boils down to running:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
in a terminal and following the prompts the installer asks. You can check Zero-to-Nix's page for more instructions, or the Determinate installer's README for detailed options. This method will work both on Linux and Mac.
Obligatory disclaimer: Nix flakes, and nix experimental commands (like nix shell
and nix profile
, which will be covered later) are experimental and may have breaking changes anytime in the future, hence, they are gated by a config option when using the default installer. But these two features have stayed experimental for many years with little-to-no breaking changes, and are thus considered de facto stable by much of the Nix community, and not using them is just giving yourself a handicap for no reason.
Using the official installer?
If you've opted to use the official Nix installer instead, you'll have to manually enable flakes for your user by editing ~/.config/nix/nix.conf
, or for all users by editing /etc/nix/nix.conf
and adding the following line to the end of the file:
experimental-features = nix-command flakes
You'll have to create the file if it doesn't already exist.
No Systemd?
If you're using a systemd-less distro, like Artix or Void, you can install Nix from their repositories, since it will come preconfigured with whatever init system you'd be using on those distros. The Determinate installer only works on distros with systemd (for a multi-user installation, which is what you want).
If you wish, you can install Nix from your distro's repositories too, instead of using the Determinate installer. I've done this on Alpine, Artix, and Arch. Just make sure the Nix package is not extremely out of date, like in Void's case. You can check if the nix
package is out of date by comparing the version in your repos to the version on NixOS's repos
Void linux's Nix package is very out-of-date (by 2-ish years!) as of the time of writing. You'll have to use the Single-user installation mode of the official Nix installer to install Nix on void.
Using Nix packages
Now that you have the Nix package manager installed, you can use it to install any package from the vast library of 120k+ Nix packages on your system, while ensuring that none of your other packages, even those installed by Nix itself, will break due to dependency conflicts.
Let's install the lolcat package. Well, Nix actually allows us to try out the package without installing it first! It downloads the package (or compiles it if the binary isn't availabe in the build cache) and drops you into a shell session containing the requested package in your $PATH
.
The below demonstrations will only have their intended effect if you don't already have
lolcat
installed!
Let's try it out! Run nix shell nixpkgs#lolcat
and Nix will download the latest commit of nixpkgs, a collection of lots of Nix packages, find the lolcat
package, get its binary from NixOS's binary cache, download it and put it in your $PATH
.
Nix will create you a new shell session where lolcat
will be available.
✨🪄 Magic! Open a new terminal window, try running lolcat
, and see that the command doesn't exist! Let's see what Nix added to our $PATH
to make lolcat
available:
$ nix shell nixpkgs#lolcat
$ echo $PATH
/nix/store/3mbkj2nlzf87aapwp1ckqrid21p9lb3j-lolcat-100.0.1/bin:... # (truncated)
Nix downloaded lolcat
and placed it in a folder in the Nix store (/nix/store
by default). The Nix store contains all packages, even multiple versions of the same package that were ever fetched by Nix. My system, about a month old, has close to 34k items in the store! Some of these are packages, some are built derivations (more on those later!). This can be cleaned using the nix store gc
command.
We'll learn more about the nix store in further articles, but for a quick rundown, the Nix store is a read-only filesystem which stores things like downloaded and built packages, any packages you create yourself, and anything else included in a nix package like downloaded or locally available source code. All packages are treated as a pure function, and their built output is stored in the store.
Notice the name of the directory where lolcat
was downloaded. It contains the hash of the derivation, ensuring integrity of the package, then the name of the package itself, and finally, the version, which in this case is 100.0.1
. When you're trying out these commands for yourself, you may have a different hash and/or version of the package.
lolcat
is specified to the nix shell
command as nixpkgs#lolcat
. This is termed as an installable, and in this case, is a flake reference (more about that later). nixpkgs#lolcat
is actually a URL with path nixpkgs
, and fragment, i.e. the part after the #
, lolcat
. nixpkgs
is an alias which resolves to the nixpkgs-unstable
branch of the Nixpkgs GitHub repository, a vast collection of Nix packages. There are other stable branches of nixpkgs, like nixpkgs-24.11
, the latest one as of writing. There are quite a few other aliases too, Nix downloads the list from this JSON file. If you omit the URL host, it defaults to the current directory (.
).
From the JSON file, the alias for nixpkgs
appears to be:
{
"from": {
"id": "nixpkgs",
"type": "indirect"
},
"to": {
"owner": "NixOS",
"ref": "nixpkgs-unstable",
"repo": "nixpkgs",
"type": "github"
}
}
The object at "to"
is one type of flake reference. It can also be written in a URL form like so: github:NixOS/nixpkgs/nixpkgs-unstable
. We'll take a look at various types of flake references later.
The fragment of the URL, lolcat
, refers to one of the packages exported by the nixpkgs
flake. Omitting it defaults to the default
package, i.e., literally a package called default
. We'll talk more about flakes later.
Actually installing lolcat
Having lolcat
in a local shell isn't really useful. Let's install lolcat
using Nix so that it can be accessed from any shell (that has the Nix profile in $PATH
!).
A nix profile is a set of packages that are installed independently from each other. Nix profiles are versioned, so you can roll back to a previous state of your profile at any time!
To install lolcat
into your profile, you can run:
nix profile install nixpkgs#lolcat
Now lolcat
will be available in any shell! To remove it, run:
nix profile remove lolcat
lolcat
will no longer be available in path, but it will still remain in the Nix store. If you wish to use lolcat
again, maybe in a temporary shell, it will not have to be downloaded/built again, since it'll already be available in the Nix store.
Notice that this time we pass the package name only, and not a flake reference. To view a list of installed packages in your current profile, run
nix profile list
.
Finally, let's demonstrate profile versioning. Run nix profile history
to see the versions of your profile:
$ nix profile history
Version 1 (2025-01-03):
flake:nixpkgs#legacyPackages.x86_64-linux.lolcat: ∅ -> 100.0.1
Version 2 (2025-01-03) <- 1:
flake:nixpkgs#legacyPackages.x86_64-linux.lolcat: 100.0.1 -> ∅
2 will be green, since it is the current version of the profile.
Don't worry about flake:nixpkgs#legacyPackages.x86_64-linux.lolcat
for now, that'll be covered later. Just notice that the first history version installed lolcat
100.0.1
, and the second history version removed it. Let's roll back to the first version with:
nix profile rollback --to 1
And lolcat
is back! If we make a change while we're in this version, Nix'll create a new version for us, without deleting version 2
, so you can roll back to any point! For now, let's stick to the same slate, and go back to version 2
. I'd leave it as an exercise for you to do the same.
If you wish to learn more about these commands, you can append
--help
to see a nicely formatted colored manual.
Nix language basics
This article will not teach you the Nix language, since there are much better places to learn that from, such as:
- https://learnxinyminutes.com/nix/ (concise)
- https://nix.dev/tutorials/nix-language (official)
- https://nixcloud.io/tour/?id=introduction/nix (interactive)
- https://nix.dev/manual/nix/2.24/language/ (the full reference)
It is highly recommended to learn this language before proceeding to the next section. If you know the language, you'll not be troubled with the syntax when you make your own flakes.
An introduction to flakes
Nix Flakes are an opinionated way to structure a nix expression made up of packages, OS configurations, development shells, modules, images, overlays, etc. If you've read the series introduction, you'll know that every .nix
file is just a Nix expression.
Before flakes were a thing, you'd have to create separate nix files for development shells (shell.nix
), packages (default.nix
), OS configurations (configuration.nix
), etc., which may get annoying, but is just a small hindrance. The real advantage of flakes is flake inputs, which let you easily fetch nix packages from anywhere using many methods ("fetchers"), and the nix
experimental commands, which are built to work with flakes.
When you create a flake.nix
file in a directory, that directory becomes a flake, so it can be used as a path in the URL passed to nix
commands. Open an empty directory on your computer, and create a blank file called flake.nix
in it. That directory has now become a flake!
We can run the simplest nix
command to verify that, nix eval
evaluates a Nix expression and prints it to stdout. Without any arguments, it evaluates the default package exported by the flake. Notice the emphasis on evaluates, not runs or installs, i.e. the command just prints the package derivation out to the screen. Some things are lazily evaluated, meaning they aren't evaluated until they're required. A package in a flake is not evaluated when you're just using the flake's development shell. It is only evaluated when you use the package, i.e. when you install it, or use it in another flake.
Running nix eval
on an empty flake.nix
however, will give you a syntax error, well obviously, since an empty file is not a valid nix expression. A flake is an attrset which has a few fields as described here, notably inputs
, a set of other flakes your flake uses, and outputs
, a function returning a set of things your flake exposes (which can be used by other flakes!). The declared flakes in inputs
will be fetched by Nix, evaluated (lazily), and passed to the outputs
function, along with a special parameter self
, which is just a reference to the set returned by outputs
, so you can reference your own flake in itself. The power of lazy evaluation!
Let's start with a simple "Hello, world!"
flake. Create flake.nix
in an empty directory on the machine you installed Nix in, and write the following code:
{
description = "My first flake!";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-24.05";
};
outputs = {nixpkgs, ...}: {
packages.x86_64-linux.hello = nixpkgs.legacyPackages.x86_64-linux.hello;
};
}
This is how a nix flake looks like. It looks quite a lot like JSON, but with semicolons instead of commas, but don't be fooled, Nix is a proper functional language with programming constructs and everything! The outputs
function shows one such construct. Nix attrsets can be condensed with dots, so nixpkgs.url = "foo";
is actually nixpkgs = { url = "foo"; };
, similarly with packages.aarch64_linux.hello
.
The function syntax of Nix is: param: returned_expr
. Nix functions can take in only one parameter, so multiple parameter functions are actually multiple functions with one parameter each, like so: param1: param2: ...: returned_expr
. Nix has support for attrset destructuring, so {nixpkgs, ...}
means that the parameter to outputs
is actually an attrset, and we extract the property nixpkgs
, which matches the input property from it. The ...
means to ignore any extra properties passed. If it isn't specified, nix will error if properties other than nixpkgs
are passed! We know that self
is another property passed to output, hence ...
is needed.
Finally, in the outputs, we define a single package named hello
for 64-bit linux systems (which is what I have), which is just set to the hello
package declared by nixpkgs
for 64-bit linux systems. Note that the legacy
in legacyPackages
doesn't actually mean that these packages are legacy (see this for an explanation [TLDR; legacyPackages
makes the nix flake show
command not evaluate the package, apart from that, they're functionally identical]). You should change x86_64
and linux
to your own system's architecture and OS, if they differ from these.
For example, an M-series Mac would be
aarch64-darwin
.
Now running nix eval
tells us that there's no default package, which is true, since we created a package named hello
, not one named default
. We could rename hello
to default
, or we could also ask nix eval
to evaluate the hello
program, since it takes an installable
as an argument:
$ nix eval .#hello
«derivation /nix/store/83p9zy4d8lh5fnipz7d1hl7g3rryw6mx-hello-2.12.1.drv»
The
.
is technically optional, but if it is omitted, bash treats#hello
as a comment, so you'll have to quote it. I prefer putting a leading.
instead of quoting the whole string.
We get a derivation! Nix saw that our flake exports the hello
package or legacyPackage, saw that it is supposed to fetch the hello
package from the nixpkgs
flakes, fetches that flake if reqiured, and returns the derivation to us.
We can also build and run this package:
$ nix build .#hello
# no output
$ ./result/bin/hello
Hello, world!
The hello
executable is actually GNU hello, that in true GNU fashion is an overly complicated program with a seventeen page manual that prints something to the screen, defaulting to Hello, world!
. Nix would build this program using its derivation from scratch, if it wasn't available in the build cache, which most packages usually are, so it downloads the program from there instead. The derivation and built (or downloaded) output is stored in the Nix store, as we saw earlier. The result
symlink also links to a folder in the store.
The result
folder is actually a symlink, which links to the built folder in the nix store:
$ readlink result
/nix/store/ccs8597k5ji5h7ad94wfr329xcxydbla-hello-2.12.1
$ tree result/
result/
|-- bin/
|-- hello
|-- share/
...
|-- man/
...
Conclusion
This was a super quick introduction to the Nix package manager and nix flakes. Stay tuned for more articles in the series, the next one lined up is about development shells with flakes. You wouldn't want to miss this one!
If you really liked this article and would like to support me, here are some ways:
Thank you so much!
Top comments (0)