What is Nix / NixOS?

Erik Krieg
6 min readFeb 12, 2024

--

Nix Github stars going up and to the right

Nix is an ecosystem of tools that provide a declarative, reproducible, isolated approach to building, installing, and configuring software that works on Linux and macOS (and Windows with WSL).

Nix goes back to 2003, when a mad scientist (Eelco Dolstra) decided building a Linux distro, programming language, and package manager might be a neat research project. It was not until the mid-2010s that Nix started gaining traction.

Nix takes an approach to package management that is novel for many, earning it some notoriety for being challenging to learn. Despite this reputation, Nix’s popularity has been growing exponentially, really picking up over the last few years (based on Github stars, at least).

I have been using Nix for some years now and would like to address one of the first points of contention newcomers face, which is trying to figure out what Nix even is. A problem that I think stems from Nix not being one single tool, but a collection of tools making up an ecosystem.

The components that make up the Nix ecosystem are:

When people talk about Nix, they could refer to any combination of these components, which can make getting started with these tools admittedly confusing.

I’ll go over each of these, looking first and in the most detail at the Nix package manager, which is the core of the ecosystem, then dipping a bit into the packages collection and the NixOS Linux distro.

Nix, the package manager

The Nix package manager is the foundation from which Nixpkgs and NixOS are built. I’ll explain a bit about the approach Nix takes to building and installing software, then describe some of the essential parts of Nix, like the Nix store, expression language, and others.

Nix’s approach to packaging software

This will be a high-level explanation of how Nix approaches packages where I’ll deliberately be avoiding some of Nix’s distinct terminology.

Building packages with Nix involves:

  • Specifying the dependencies and build steps using the Nix language
  • Building packages from source, as opposed to downloading binaries
  • Reproducible build outputs
  • Optionally isolated build environments

While you could write your custom Nix expressions for everything, one typically creates on top of Nixpkgs, which contains build instructions for many programs that you will likely need as dependencies for building your own packages. There are also community-maintained collections of packages out there.

Installing packages with Nix involves:

  • Building dependencies and output package (if not already present in your Nix store)
  • Storing package build output at a unique path, like /nix/store/3cjg975903ifkq3nx3a3xmkdq7zb5ycq-${PACKAGE_NAME}-${PACKAGE_VERSION}
  • Creating immutable files in the Nix store (which helps make rollbacks easier)
  • Updating a Nix profile containing symlinks to executables

Parts of the Nix Package manager

I’m breaking down the package manager into:

  • Nix expression language
  • Nix store
  • Nix CLI

Nix Expression Language

Nix has a functional programming language used to write package build specifications, but it is actually a Turing-complete language.

Here is a silly example of Nix (language) solving Advent of Code’s day 1, part 1 problem and shows a few of the language features:

with builtins;
let
# Imports several functions into scope
inherit (import <nixpkgs/lib>) toInt;
inherit (import ../../lib) debug lines sum;

test = (getEnv "TEST") == "true";
test_input = ''
1abc2
pqr3stu8vwx
a1b2c3d4e5f
treb7uchet
'';

# These are functions being declared that both take an argument called "s"
firstDigit = s: match "[a-z]*([1-9]).*" s;
lastDigit = s: match ".*([1-9]).*" s;

inputs = lines (if test then test_input else readFile ../../inputs/day-1.txt);
digits = debug (map (x: toInt (head (firstDigit x) + head (lastDigit x))) inputs);
result = sum digits;
in
result

Full code here.

Here is a simple demonstration of how you might write a Nix expression that can build a package, which Nix calls derivations:

# Import the Nix Packages collection
with import <nixpkgs> {};

# Define a new derivation using `stdenv.mkDerivation`.
# The `rec` keyword allows us to access attributes within the set.
stdenv.mkDerivation rec {
# The name of the package
name = "my-package";

# The version of the package
version = "1.0.0";

# Source code location (you can also use fetchFromGitHub, fetchurl, etc.)
src = ./path-to-source;

# Dependencies required to build the package
buildInputs = [ gcc glibc ];

# Shell commands to execute during the build phase
# This is where you would typically compile the source code.
buildPhase = ''
mkdir -p $out/bin
cc -o $out/bin/my-program src/main.c
'';

# Installation commands (typically copying files to the output directory)
installPhase = ''
mkdir -p $out/bin
cp my-program $out/bin/
'';

# Metadata about the package
meta = {
description = "A simple Nix package example";
homepage = "https://example.com";
license = lib.licenses.mit;
};
}

These build expressions can get pretty complex, especially if you don’t come from a functional programming background. A lot of Nix’s learning curve comes down to the fact that you have to learn a new programming language before you can really be effective with Nix.

Nix Store

The Nix store is a volume mounted at /nix that contains Nix package definitions as well as build outputs from the packages you’ve previously built. The file system on this volume is treated as immutable (they won’t be changed after creation outside of being deleted), which helps enforce the package manager’s reproducibility and rollback feature. Here symlinks are used to great effect to help reduce duplication.

Command Line Interface (CLI)

The last component is Nix’s CLI, which helps you interact with Nix expressions and your Nix store. There are too many commands to go over in this little article, but if you are interested: https://nixos.org/manual/nix/stable/command-ref/

A couple points concerning the CLI I’d like to draw attention to:

  1. There is an extensive set of experimental commands
  2. There are commands for imperatively installing specific packages

First, the experimental commands are associated with a newer development in the Nix language called Flakes. Flakes are to Nix expressions sort of like package.json files are to NodeJS. They serve as an entry point for running a Nix build, and help lock build inputs, such as the specific version of Nixpkgs used. The community appears torn on whether or not Flakes are a good idea. I use them and feel like they helped me better understand how to use the Nix language. Consuming a lot of Nix-related content, it also seems like the momentum behind Flakes continues to grow.

Next, the CLI includes commands like nix-env and nix profile install which offer ways to install a package to your system. Nix offers more idiomatic ways to install and manage packages using Nix expressions. NixOS, for instance, provides the nixos-rebuild switch command (macOS can do similar with nix-darwin) and can configure your entire system, which is amazingly powerful and strongly preferred over ad-hoc installs via CLI.

Nixpkgs

Nixpkgs is a collection of derivations that let you install a wide range of software. You can search to see if what you’re looking for is there. The collection contains over 80,000 packages, but if you need something that isn’t there, it is up to you to build your derivation (or find one offered by a community collection).

Nixpkgs also contains a substantial set of Nix language utility functions that can help make writing your derivations much easier. For example, Nixpkgs provides language-specific functions like buildRustPackage or buildGoModule which deal with a lot of boilerplate for compiling Rust or Go projects respectively.

NixOS

NixOS is the Linux distribution built around the Nix package manager (the source of which also lives with Nixpkgs). It takes Nix further by not only dealing with building and installing packages but configuring your file systems, networking, users, services (like with systemd) and more.

These configurations would generally exist as Nix expressions (potentially using Flakes) and built and applied with nixos-rebuild commands.

For anyone sufficiently curious, I have some NixOS machines configured here.

Conclusion

The Nix package manager lies at the core of the Nix ecosystem, offering a distinct approach for building and installing software, or even configuring Linux and macOS machines. Hopefully I have helped clear up some questions about Nix; and hopefully you’ve been left curious to know more.

Learning Nix is tricky, but in my experience, very rewarding. If you want more details, here are some things I can recommend that go deeper:

--

--

Erik Krieg

Technology-person currently doing DevOps stuff & Site Reliability Engineering things