Skip to content

Nix / NixOS - Primer

Why This Matters

Nix eliminates "works on my machine" permanently. Every build input is hashed and isolated, so if it builds on one machine, it builds identically on any machine. For DevOps teams, this means reproducible dev environments, hermetic CI pipelines, and deterministic deployments.

Name origin: Nix is named after the Dutch/German word for "nothing" — reflecting its philosophy of building everything from scratch in a clean, isolated environment. Created by Eelco Dolstra as part of his 2006 PhD thesis at Utrecht University.

Fun fact: Nixpkgs is the largest package repository in existence — over 100,000 packages, more than Debian, AUR, or Homebrew. Packages are defined in the Nix language (a lazy, pure, functional language).

Core Concepts

Nix store — immutable content-addressed storage at /nix/store/<hash>-<name>. Every package, dependency, and build artifact lives here. Paths are never mutated after creation.

Under the hood: The hash in a store path is derived from ALL inputs: source code, compiler version, build flags, and every dependency. Change any input and you get a different hash — and a different store path. This is how Nix achieves bit-for-bit reproducibility and allows multiple versions of the same package to coexist.

Derivations — build recipes that declare inputs, build steps, and outputs. A derivation's output path is determined entirely by its inputs, guaranteeing reproducibility.

Analogy: A derivation is like a recipe card that lists every ingredient with exact brands and quantities, plus every step. Two people following the same recipe card produce identical results — because the "recipe" includes even the oven brand (compiler version). If you swap one ingredient, you get a completely different dish (a different store path hash).

Flakes — the modern entry point for Nix projects. A flake.nix declares inputs (e.g., which nixpkgs revision) and outputs (packages, dev shells, NixOS configs). A flake.lock pins exact revisions, like a lockfile for your entire toolchain.

Gotcha: Flakes are still technically "experimental" (behind --experimental-features 'nix-command flakes'), but they are the de facto standard in the Nix ecosystem since ~2022. Almost all new Nix projects use flakes. The old default.nix / shell.nix approach still works but lacks the lockfile and input-pinning benefits.

nix develop / nix-shell — drop into a shell with specific packages available without installing them globally. nix develop uses flakes; nix-shell is the legacy equivalent.

One-liner: nix-shell -p python3 nodejs — instantly get Python and Node in your shell without installing anything permanently. Exit the shell and they vanish. This is the fastest way to test a tool without polluting your system.

DevOps Value

  • Reproducible environments: every developer and CI runner uses bit-identical tooling via flake.lock.
  • Dev shells: onboard new engineers with nix develop — no manual setup, no version drift.
  • CI caching: Nix's content-addressed store means unchanged derivations are never rebuilt. Binary caches (Cachix, self-hosted) share build results across machines.

Default trap: Nix's learning curve is steep. The Nix language (lazy, pure, functional) is unlike anything most DevOps engineers have seen. Error messages are notoriously cryptic. Start with nix-shell and pre-built flake templates before trying to write your own derivations. The community recommends https://zero-to-nix.com as an onramp.

Remember: The Nix mental model mnemonic: "DISH" — Derivations (build recipes), Immutable store (content-addressed /nix/store), Shell environments (nix develop), Hash everything (inputs determine output path). If you remember DISH, you understand Nix's architecture.

Minimal Example

A flake.nix that gives your team Node.js and Python in one command:

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
  outputs = { self, nixpkgs }:
    let pkgs = nixpkgs.legacyPackages.x86_64-linux;
    in {
      devShells.x86_64-linux.default = pkgs.mkShell {
        packages = [ pkgs.nodejs_20 pkgs.python311 ];
      };
    };
}

Run nix develop in the repo root. Everyone gets the same versions, every time.

See Street-Level Ops for operational commands and Footguns for common pitfalls.


Wiki Navigation

Prerequisites