So, you clicked on a Nix article, eh? That means you’re either completely lost, or is a huge tech nerd like me. Either way, you’re definitely welcome here. As the title suggests, I’ll do my best to try and convince you to give Nix a chance, and explore the endless possibilities it can give you. Whether you’ve never heard of it, or already are Nix-curious and waiting for an oportunity to start learning, this is for you.

I’ve heard about this Nix thing from a few friends, but never quite understood the hype.
Perfect! I’ll do my best to tell you why you might want to give it a go.

Oh, me? I already am using Nix. I’ve even developed a new implementation for a flake-based configuration where—
Well, then you could follow along for the possibility of spotting and correcting my dumb mistakes, or you could share this with someone you’re trying to convince to use Nix, but there will probably be little learning for you here. Anyway, send me the coolest Nix-related thing you know of.
I just need to make this clear: this is not a Nix tutorial, it’s more like a sales pitch. I won’t be teaching you how to use Nix in a meaningful way, I’ll just show you enough so you’ll want to learn it yourself. If you actually want a Nix tutorial, you can go to the official docs for a comprehensive starting point, to Nix pills to learn about how it all works, or to Zero to Nix for an easily digestible starting point for a total newbie.
Also, I’m mostly talking about Nix, the package manager, not just the linux distribution, NixOS. You can try Nix on most popular distributions right now.
First, the why
“Why would I spend so much time learning about such an esoteric tool? What value does it bring me?” you may ask. First, let me describe a few software-related nightmares before I can show you how Nix can banish them:
You’re trying to use an older piece of software. Something that’s been written in the dark ages of Node 8 or Python 2, or maybe it’s just been a week and one of its dependencies already released a breaking change that blows everything up. Either way, you want to use that software no matter what, and more power to you for doing so. But those specific versions of its runtime dependencies are no longer available on your package manager’s repositories. You might want to try downgrading them, but there’s a non-zero chance that’ll break the rest of your system. So you decide your best chance is to build them from source. But the dependencies’ dependencies are also not up to date, so you’d have to build those from source too, and then—

We don’t have to live in dependency hell. Nix can fix that!
Here’s another scenario: you’re a developer writing a piece of software that has many specific dependencies, and you want not only to properly document them, but to provide a quick and easy development environment for other contributors. You then decide to write a section in the README describing which versions of what dependencies are required, and if you’re feeling generous, you might even document the package names of those dependencies on the most popular distros. But at the end of the day, it’s an exercise to the reader to get everything correct with the right version on their system.

Sudenly, my desire to contribute to the project has completely vanished…
Fear not! There is a better way! With Nix!
A third scenario, if you’re still not hooked: You’re an enthusiast who loves trying out new software. You’re constantly installing, configuring, tweaking… but you keep forgetting what you last used, or what last configuration worked well with it, or to delete software that you’re not using anymore, or to…

What was that last line I had to add to /etc/fstab to make my flash drive auto-mount again?
Well, as you might guess… Nix can fix that!
Okay, I’m interested… But what is this Nix?
Nix is, fundamentally, a build tool. In its most absolutely basic form, You’d
write derivations, which are a special kind of Nix object that has all the
necessary instructions and dependencies to build some piece of software. A
derivation is, usually, just a bash script that invokes some tools (such as
gcc
, cargo build
, or anything like that) on some source code. The action of
building a derivation is to execute these instructions to generate the built
software.

Wait, so Nix just calls a bash script to build my stuff? How is that better than just using the script myself?
Well, Nix builds stuff in a deterministic way (not really, but it is in all the ways that matter for this article). It takes in some inputs, hashes them, and uses that hash to generate an output directory. If anything about the inputs change — anything at all — the output directory will also change. This means that, for every unique set of inputs, there will be a unique output directory.
An input is anything that is needed to build your code, including the source code, build tools, build commands, dependencies, and even the name and version you give your package. If any of these change, the hash will change, and you’ll get a whole new build in a new directory.

Okay, so, if I understand it correctly, you tell Nix how to build your package, and it’ll build it in a unique directory. This still kinda sounds like a regular package manager. How does this help to solve the problems we were discussing earlier?
Let’s take, as an example, the first problem we raised: we’re trying to run a piece of software (Let’s call it Foo) that needs a specific version of its runner (E.g. Node 8). Since Nix uses the source code in the output directory hash, Node 8 will be built in a different directory from Node 12, or Node 16, or any other version. We can, then, tell Nix to use Node 8 for just building Foo, and use the latest version of Node for the rest of the system. Those two versions of Node can coexist on the same system without conflict.
Since the list of dependencies is also part of the hash, we could even build Foo with different versions of its dependencies. You could build one version with Node 12, another with Node 18, and yet another with Node 22. They would all live in different output directories, without any conflict. You could, for a real world example, try building curl with Musl instead of Glibc, or build wget with an older version of openssl to test some vulnerabilities. These frankenstein versions of the packages would have their own separate directories, and could be used independently from their regular versions. Try doing that in a regular package manager. I dare you.
Solving our problems with Nix

Okay, so Nix is a fancy build tool to build software. We already have loads of those. How is Nix better? And how can it solve so many problems?
When all you have is a hammer, all your problems turn into nails. The trick is to realize that a lot of problems can be boiled down to what built tools already do best.
We already explored how Nix can help us build multiple versions of the same software. That wasn’t incredibly exciting, since it’s what we expect to be within the realm of possibilities of a build, but here’s how Nix can help us solve the other problems that were brought up at the start of this article:
Running older software
nixpkgs is Nix’s largest package repository, maintained by the Nix team themselves. It has derivations for most software out there. The nice thing about it is that it’s a github repository, which means we can access older versions of it. Running older versions of software, then, is just a matter of figuring out what commit of nixpkgs has that version. nixhub.io is a website exclusively dedicated to that purpose. For example, commit d6d07f2 has sqlite version 3.31.1 and commit e912fb8 has lazygit version 0.20.4
So, as an example, running curl version 7.81.0 (which was released January 2022) would be simply a matter of running:
$ nix run github:nixos/nixpkgs/98bb5b77c8c6666824a4c13d23befa1e07210ef1#curl -- --version
curl 7.81.0 (x86_64-pc-linux-gnu) libcurl/7.81.0 OpenSSL/1.1.1m zlib/1.2.11 brotli/1.0.9 zstd/1.5.1 libidn2/2.3.2 libssh2/1.10.0 nghttp2/1.43.0
Release-Date: 2022-01-05
Protocols: dict file ftp ftps gopher gophers http https imap imaps mqtt pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp
Features: alt-svc AsynchDNS brotli GSS-API HSTS HTTP2 HTTPS-proxy IDN IPv6 Kerberos Largefile libz NTLM NTLM_WB SPNEGO SSL TLS-SRP UnixSockets zstd
If the software you’re trying to run is not on nixpkgs, and requires very specific versions of its dependencies, you can just pull these versions from nixpkgs and manually build the software. You could even write your own derivation for it, which would automate the build process.
Providing a quick and easy development environment for writing software
Your development environment is, at the end of the day, a Bash shell with some
environment variables set (The PATH
in particular). Setting up that
environment, then, is just a matter of building a script that puts you into a
shell with those variables set.
This is essentially just writing a script that modifies the appropriate variables and calls another instance of bash with those new variables, like so:
PATH="/path/to/tool1:/path/to/tool2:/path/to/tool3:$PATH"
# This new bash instance will have an updated path
bash
Say we’re writing a software that runs on Node.js and sqlite. Here’s how we’d write a derivation to setup a development environment:
# File default.nix
let
pkgs = import <nixpkgs> {};
in pkgs.mkShell {
buildInputs = [
pkgs.nodejs
pkgs.sqlite
];
}
Then, we could run this with nix-shell ./default.nix
(or simply nix-shell
).
This would put us into a shell with access to the node
command and the
sqlite
command, along with access to sqlite’s shared library objects.

But what if I want to use a specific version of a program or library?
Through the magic of flakes, you can! Here’s a flake that’ll give you nodejs version 12.18.0 and python version 2.7.18.8 (on a linux machine)
# Save this as flake.nix
{
inputs = {
nixpkgs-node12.url = "github:nixos/nixpkgs/9480bae337095fd24f61380bce3174fdfe926a00";
nixpkgs-python2.url = "github:nixos/nixpkgs/9b008d60392981ad674e04016d25619281550a9d";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs =
{
nixpkgs-unstable,
nixpkgs-python2,
nixpkgs-node12,
...
}:
let
system = "x86_64-linux";
pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
pkgs-pyton2 = nixpkgs-python2.legacyPackages.${system};
pkgs-node12 = nixpkgs-node12.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs-unstable.mkShell {
buildInputs = [
pkgs-node12.nodejs-12_x
pkgs-pyton2.python2
];
};
};
}
Then just run nix develop
and 💥Boom💥 (If you have a sane configuration,
you might need some extra fluff to run this:
NIXPKGS_ALLOW_INSECURE=1 nix develop --impure
). Yes, this is insane. Madness.
But if you need this, here it is. You’ll always get the same version of these
packages.

I pray I’ll never need this, but I’m happy to know it’s here.
Easily try out and install software
You want to try out some software? Don’t want to remember to clean it up later?
Sure! Go on search.nix.org and see what’s its
package name in nixpkgs. Then, just run nix shell nixpkgs#NAME_OF_PACKAGE
. For
example, to get nodejs, you can do nix shell nixpkgs#nodejs
.
You’ll then be dropped into a shell with node available. Want to give
lazygit a try? Sure, here you go:
nix shell nixpkgs#lazygit
. What about Gimp?
Absolutely! nix shell nixpkgs#gimp
.
When you’re done, simply exit
from your shell, or close your terminal. It’ll
not be available in your PATH anymore, and it’ll be gone in the next
garbage collection.

Well, you could mess around with profiles, but that’s kinda the same thing a regular package manager already does. Instead, Nix’s magic solution is in ✨Declarative Package Management✨ .
If you think about it, “installing software” is essentially just generating some binaries (either through compilation or through downloading them from the internet) and storing them (or symlinking them) in a directory that’s available to your PATH environment variable. If you think about it even harder, this directory is kind of like a package: it has dependencies, and its “build script” would be to just make a symlink for each dependency. Kinda like this:
bin="/path/to/bin"
mkdir "$bin"
ln -s /path/to/software1 "$bin/software1"
ln -s /path/to/software2 "$bin/software2"
ln -s /path/to/software3 "$bin/software3"
# ... Do this for every software you want available ...
PATH="$bin:$PATH"
# This new bash instance will have software1, software2 and software3 available in its $PATH
exec bash
In short, you can represent your “installed packages” as a Nix derivation that
builds a bin
directory. This derivation is just Nix code, in a file, trackable
by Git. If you add or remove any package, you can simply rebuild your bin
directory. This means you have a single file representing your entire user
space, which can be version controlled, so you could see which commit added or
removed any software. Here’s how you do it in a regular NixOS system:
{
config,
pkgs,
...
}:
{
environment.systemPackages = with pkgs; [
wget
git
neovim
curl
# ... Add all your software here...
];
}
Want to remove curl and add httpie? Sure, here you go!
{
config,
pkgs,
...
}:
{
environment.systemPackages = with pkgs; [
wget
git
neovim
- curl
+ httpie
# ... Add all your software here...
];
}

Wait, won’t removing curl from my system break something? Isn’t curl a dependency for a lot of stuff?
That’s the beauty of it: no! Remember, we’re only building the directory that’ll
be used in our $PATH
environment variable. If another package needs curl to
function, Nix will install curl, make it available to that package, but won’t
add it to your $PATH
, and won’t polute your environment.
There are more details about this process, such as making sure things like man pages are available too, but this is the essence of it.
Overriding dependencies from software
Have you ever wanted to run nodejs using musl instead of glibc? Or build an older version of curl using the current version of zlib? Or maybe you want to build openssh using an older (and vulnerable) version of openssl?

No? What kind of psycho wants to do any of this?
Ok, ok. I know this is very unusual and mostly useless, but it’s still neat and I think it’s worth showing here. You can override a derivation’s dependencies, and replace them with anything you want, including older versions of these dependencies.
Here’s an example of a flake that builds wget with openssl version 1.1.1q (from 2022):
{
inputs = {
nixpkgs-openssl.url = "github:nixos/nixpkgs/5f326e2a403e1cebaec378e72ceaf5725983376d";
nixpkgs-unstable.url = "github:nixos/nixpkgs/nixpkgs-unstable";
};
outputs =
{
nixpkgs-unstable,
nixpkgs-openssl,
...
}:
let
system = "x86_64-linux";
pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
pkgs-openssl = nixpkgs-openssl.legacyPackages.${system};
in
{
packages.${system}.default = pkgs-unstable.wget.override {
openssl = pkgs-openssl.openssl;
};
};
}
Conclusion
I’ve only even scratched the surface of what Nix can do. There are things like cross-compilation, generate ISO images of completely custom distributions, use home-manager to write config files for most of your software, and many other cool things. These are, however, more advanced use cases that escape the scope of this article. I hope I sparked enough of a flame in your heart to go after Nix knowledge yourself, and try this amazing tool that enables so many possibilities.