Preface
[TODO]
Introduction
This book is about using Nix to help anyone use software in a safer, more useful, more reproducible way, and having fun and getting things done while doing it. For a technology that is notoriously difficult to learn and use, this is usually considered no easy task! But my goal is that if you start and stay on this journey with me, you'll finish it wondering how you ever lived without Nix.
When we refer to Nix in the software world, we're really referring to a set of three interrelated technologies: Nix the programming language, Nix the package manager, and Nix the operating system (NixOS). NixOS depends on the Nix package manager and the package manager depends on the language.
They were created with an ambitious and noble goal: to solve the software deployment problem, concisely defined this way by Nix's creator:
[The software deployment problem] is about getting computer programs from one machine to another—and having them still work when they get there.
The Purely Functional Software Deployment Model, Eelco Dolstra
This is a wildly thorny problem in computer science, and has been the cause of an incredible amount of pain for software developers and users alike.
This pain is the result of how software is usually deployed by package managers. A package manager is a tool which installs, updates, configures, and removes software packages on a computer. Nearly all in use today do this in a non-reproducible way, meaning that if you get a software program working on your machine, it isn't guaranteed to work on mine, even if I follow the same steps you did. In fact, doing any one of the install, update, configure, or removal steps could result in broken software, or even a broken system.
But with Nix, these problems (mostly) disappear. This is because Nix manages packages in a reproducible way: what works on your machine will work on mine -- as long as I have Nix. The full consequences of this are hard to fully articulate, and so this book will go to lengths to show as well as tell you how this is safer, more useful, and (in my opinion) more fun as we learn.
To whet your appetite for what's possible, let's do something fun with Nix. Don't worry about the details of what's going on here yet, feel free to just copy and paste the code snippets and we'll explain them in detail later.
Say we have a fun little bash script like this:
#!/usr/bin/env bash
# filename: pokefortune.sh
message="${1:-}"
pokemon="${2:-slowking}"
# Silence Perl locale warnings.
export LC_ALL=C
# Generate a fortune if user did not pass a message.
if [[ -z "$message" ]]; then
message=$(fortune)
fi
echo "$message" | pokemonsay -p "$pokemon" -n
It optionally takes in a message and a Pokédex number, and prints out that Pokémon and message using the pokemonsay program. If either the message or the Pokemon aren't specified, it uses a default Pokemon and the program fortune to generate the message.
Save this in a file pokefortune.sh, make it executable, then run it:
# Make the bash script executable
chmod +x pokefortune.sh
./pokefortune.sh
If you're like most people, you probably don't have pokemonsay or fortune installed on your system, so you'll likely see something like this:
./pokefortune.sh: line 6: fortune: command not found
./pokefortune.sh: line 7: pokemonsay: command not found
Therefore this script, which works on my system because I have these programs installed, isn't reproducible on your system. Let's create a Nix derivation to make it so. Create a file called pokefortune.nix and copy the following:
# filename: pokefortune.nix
{
pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") { },
}:
pkgs.writeShellScriptBin "pokefortune" ''
message="''${1:-}"
pokemon="''${2:-slowking}"
# Silence Perl locale warnings.
export LC_ALL=C
# Generate a fortune if user did not pass a message.
if [[ -z "$message" ]]; then
message=''$(${pkgs.fortune}/bin/fortune)
fi
echo $message | ${pkgs.pokemonsay}/bin/pokemonsay -p "$pokemon" -n
''
Now run:
nix-build pokefortune.nix
This may take awhile, especially if this is your first time running Nix. Let's look at the output:
nix-build src/scripts/nix/pokefortune.nix
unpacking 'https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz' into the Git cache...
this derivation will be built:
/nix/store/vg4zmghqcnhjbs8kqhx04xixvm36d3ik-pokefortune.drv
these 9 paths will be fetched (2.65 MiB download, 26.91 MiB unpacked):
/nix/store/0vdf5mpd762bw53rgl5nkmhvzq8n4m0d-file-5.45
/nix/store/77cdqhqprqbciyhzsnzmsk7azbk2xv6r-fortune-mod-3.20.0
/nix/store/d0i8idmbb4jji9ml01xsqgykrbvm7dss-gnu-config-2024-01-01
/nix/store/0554jm1l1qw1pcfqsliw91hnifn11w8m-gnumake-4.4.1
/nix/store/2wp235bg03gykpixd9v2nyxp08w8xq8a-patchelf-0.15.0
/nix/store/c5x32idp600dklz9n25q38lk78j5vwxb-pokemonsay-1.0.0
/nix/store/pba53n11na87fs4c20mp8yg4j7qx1by2-recode-3.7.14
/nix/store/zix67r268ihi4c362zw7c0989z12jmy7-stdenv-linux
/nix/store/2329271b42wh6b6yhl7jmjyi0cs4428b-update-autotools-gnu-config-scripts-hook
copying path '/nix/store/d0i8idmbb4jji9ml01xsqgykrbvm7dss-gnu-config-2024-01-01' from 'https://cache.nixos.org'...
copying path '/nix/store/c5x32idp600dklz9n25q38lk78j5vwxb-pokemonsay-1.0.0' from 'https://cache.nixos.org'...
copying path '/nix/store/0vdf5mpd762bw53rgl5nkmhvzq8n4m0d-file-5.45' from 'https://cache.nixos.org'...
copying path '/nix/store/0554jm1l1qw1pcfqsliw91hnifn11w8m-gnumake-4.4.1' from 'https://cache.nixos.org'...
copying path '/nix/store/2wp235bg03gykpixd9v2nyxp08w8xq8a-patchelf-0.15.0' from 'https://cache.nixos.org'...
copying path '/nix/store/pba53n11na87fs4c20mp8yg4j7qx1by2-recode-3.7.14' from 'https://cache.nixos.org'...
copying path '/nix/store/2329271b42wh6b6yhl7jmjyi0cs4428b-update-autotools-gnu-config-scripts-hook' from 'https://cache.nixos.org'...
copying path '/nix/store/zix67r268ihi4c362zw7c0989z12jmy7-stdenv-linux' from 'https://cache.nixos.org'...
copying path '/nix/store/77cdqhqprqbciyhzsnzmsk7azbk2xv6r-fortune-mod-3.20.0' from 'https://cache.nixos.org'...
building '/nix/store/vg4zmghqcnhjbs8kqhx04xixvm36d3ik-pokefortune.drv'...
/nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune
Nix fetched the packages we specified in our .nix file: pokemonsay, fortune, and some we didn't specify: such as file, patchelf, and recode, which one or both of the other two packages depends on. Then it copied them locally, built a binary, and placed it at /nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune/bin. We can confirm this by running it:
/nix/store/vsv2spw517cwq791fl3f8iymm6hshhyq-pokefortune/bin
You should see something like this:
To be reproducible, Nix ensures that it knows about every single dependency needed to create a package at all times. Because of that, you can do fun things like this:
# Quickly enter a shell with the dependencies to create and display images.
nix-shell -p graphviz chafa
# Query the derivation's dependency graph, create a .png from it and display it:
nix-store --query --graph $(nix-build pokefortune.nix) | \
dot -Tpng -o pokefortune-dependency-graph.png && chafa pokefortune-dependency-graph.png
Resulting in a view of every single dependency (the "closure") that our pokefortune program requires:
How cool is that! We can see the whole dependency tree for anything packaged with Nix!
Congratulations! You have just done something very powerful with Nix: You've created a reproducible derivation that anyone who has the package manager installed on their system can use, which they couldn't before.
This is just a taste of what Nix can do, but I hope that the potential is clear. As Farid Zakaria says in his blog post "Learn Nix the Fun Way":
Hopefully, seeing the fun things you can do with Nix might inspire you to push through the hard parts.
There is a golden pot 💰 at the end of this rainbow 🌈 awaiting you.
References:
- https://edolstra.github.io/pubs/phd-thesis.pdf
- https://web.archive.org/web/20171017151526/http://aptitude.alioth.debian.org/doc/en/pr01s02.html
- https://fzakaria.com/2024/07/05/learn-nix-the-fun-way
- https://zero-to-nix.com/start/nix-run/
Chapter 1: The Nix Language
[TODO]