# Nix Primer Nix is **two things bundled**: 1. **A pure, lazy functional language** for describing builds. 2. **A package manager** that takes those descriptions and produces content-addressed store paths. Confusingly, both are called "Nix." Separate them in your head and everything gets easier. --- ## 1. The mental model: eval, then build Every `nix build` / `nix develop` runs in two phases: ``` .nix source ──(evaluate)──▶ .drv file ──(realise)──▶ /nix/store/...-output │ │ │ └── Nix language └── Recipe └── Actual files (pure, lazy) (on disk) (content-addressed) ``` - **Evaluation** runs the Nix language. Pure, in-memory, fast. Produces `.drv` files. - **Realisation** actually builds things. Sandboxed. Slow. Outputs go to `/nix/store`. You can evaluate without building (`nix eval`, `nix-instantiate`) and you can build without touching the language if you already have a `.drv`. Most errors you'll see are *eval* errors — typos in attrsets, missing arguments, undefined names. --- ## 2. The language in 5 minutes ### Primitives and collections ```nix 42 # integer 3.14 # float "hello ${name}" # string with interpolation ''multi-line # indented string (strips common leading whitespace) string'' true false null [ 1 2 3 ] # list (space-separated, NOT comma) { a = 1; b = "two"; } # attrset /foo/bar # path (literal — not a string!) ./relative/path # path relative to current file ``` Paths are their own type. When used, they're copied into the store and replaced with the store path. ### Functions Single-argument, curried: ```nix add = x: y: x + y; add 2 3 # => 5 ``` Attrset destructuring (how "named args" work): ```nix mkUser = { name, age ? 0, ... }: { inherit name age; }; mkUser { name = "ada"; } # => { name = "ada"; age = 0; } ``` The `...` means "ignore extra attrs." Without it, passing extras is an error. ### Bindings ```nix let x = 1; y = x + 1; # can reference earlier bindings (and each other — lazy) in x + y ``` ```nix { inherit x y; } # ≡ { x = x; y = y; } { inherit (pkgs) hello jq; } # pulls hello/jq from pkgs into this attrset ``` ### `with` (use cautiously) ```nix with pkgs; [ hello jq ripgrep ] # ≡ [ pkgs.hello pkgs.jq pkgs.ripgrep ] ``` Convenient in package lists. Avoid at the top of a file — it silently shadows names and makes debugging hard. ### Laziness Nothing is evaluated until needed. This is why you can have huge attrsets like `nixpkgs` (100k+ packages) and only pay for what you use. ```nix let broken = throw "nope"; ok = 1; in ok # => 1, never evaluates `broken` ``` ### `import` ```nix import ./foo.nix # evaluate foo.nix, return its value import ./foo.nix { x = 1; } # if foo.nix is a function, also call it ``` --- ## 3. Derivations — the core primitive A **derivation** is "a build, described as data." You create one with `derivation { … }` (low-level) or `pkgs.stdenv.mkDerivation { … }` (the nixpkgs wrapper you'll actually use). Minimal example: ```nix { pkgs ? import {} }: pkgs.stdenv.mkDerivation { pname = "hello-note"; version = "0.1"; src = ./.; # path — gets copied into the store installPhase = '' mkdir -p $out/bin echo '#!/bin/sh' > $out/bin/hello-note echo 'echo hello from nix' >> $out/bin/hello-note chmod +x $out/bin/hello-note ''; } ``` Key ideas: - **`$out`** is the output store path the build writes into. - **Phases** (`unpackPhase`, `buildPhase`, `installPhase`, …) are shell snippets `stdenv` runs in order. Override the ones you need. - **No network** in the build sandbox, except for fixed-output derivations (whose hash you declare up front). - **Every input** — source, compiler, env vars — becomes part of the hash. Change anything and you get a fresh store path. --- ## 4. Store model: why this is all different Traditional package managers put files in `/usr/bin`, `/usr/lib`, etc. One version at a time. Upgrades mutate shared state. Nix puts every build in `/nix/store/-/` with the hash derived from **all its inputs** (recursively). That gives you: - **Atomic upgrades / rollbacks** — switching "versions" is just switching symlinks. - **Multiple versions coexisting** — they live at different hashes. - **Reproducibility** — same inputs → same hash → same output (in theory; in practice, 99% there). - **Binary caching** — if someone else already built exactly these inputs, you can download their output. This is what `cache.nixos.org` does. - **Garbage-collectable** — anything not referenced by a "GC root" can be deleted safely. --- ## 5. How you'll actually use Nix day-to-day Ordered roughly by power-to-complexity: 1. **`nix run nixpkgs#cowsay -- moo`** — run a package without installing. 2. **`nix shell nixpkgs#jq nixpkgs#ripgrep`** — throwaway shell with those tools. 3. **Dev shell via `flake.nix`** — per-project pinned toolchain. *This is the most common thing flakes are used for.* 4. **`nix build`** — produce artifacts (binaries, containers, ISOs). 5. **NixOS** — declare your whole OS config (packages, services, users, network) in Nix. 6. **home-manager** — same idea, at the user level. Each step up locks in more of your environment. You don't need to adopt all of it at once. --- ## 6. Minimum useful commands ```bash # Language / introspection nix repl # REPL. Try `:l ` then `hello`. nix eval --expr '1 + 1' nix-instantiate --eval -E '{ a = 1; }.a' # Running & building nix run nixpkgs#hello nix shell nixpkgs#jq nix build nixpkgs#hello # writes ./result symlink nix-store --query --references ./result # show closure # Housekeeping nix store gc # garbage collect nix store optimise # dedupe identical files via hardlinks nix why-depends ./result nixpkgs#glibc # trace dependency chains ``` --- ## 7. Gotchas that trip up newcomers - **Lists use spaces, not commas.** `[ 1 2 3 ]`. Commas are a syntax error. - **`rec { }`** makes an attrset self-referential. Without `rec`, attrs can't reference each other. You'll see this in package definitions. - **Paths vs. strings.** `./foo` is a path (copied to store on use). `"./foo"` is a string (not auto-copied). Mixing them causes surprises. - **`builtins.*`** is the always-available low-level namespace. `lib.*` (from nixpkgs) is the high-level helper library. Both exist; they're different. - **Evaluation errors are lazy.** An error deep in an unused branch only fires when you actually use it. `nix-instantiate --eval --strict` forces full evaluation. - **Hash errors on first build of an FOD**: you'll see `got: sha256-…`. Copy that hash into your expression. This is expected workflow. --- ## 8. What to read/try next - Open `nix repl`, type `:l `, then `hello`, `hello.drv`, `hello.outPath`. Poke around. - Read [`nix.dev`](https://nix.dev) — the current best introduction. - See `003-flakes.md` for the flakes-specific layer on top of all of this.