This commit is contained in:
Hassan Abedi 2026-04-15 15:47:10 +02:00
parent d2814acdc9
commit 66d368555d
3 changed files with 75 additions and 75 deletions

View File

@ -4,47 +4,47 @@ Core vocabulary you'll hit in the first hour of Nix. Skim, don't memorize; come
## The Store and Builds
- **Nix store**: `/nix/store`. Immutable, content-addressed directory where *everything* Nix builds lives. Each entry is prefixed by a hash of its inputs.
- **Store path**: a single entry under `/nix/store`, e.g. `/nix/store/abc123…-hello-2.12.1`. The hash makes it unique per set of build inputs.
- **Derivation (`.drv`)**: a *recipe* for a build, covering inputs, builder script, env vars, and outputs. Produced by evaluating Nix code. Not the built artifact itself.
- **Realisation / "realising a derivation"**: actually executing the `.drv` to produce the output store path(s). "Build" colloquially means this.
- **Output path**: the store path a derivation produces. A derivation can have multiple outputs (e.g. `out`, `dev`, `man`, `lib`).
- **Closure**: a store path plus all its runtime dependencies, transitively. What you need to copy to another machine to actually use something.
- **Hash**: content/input hash that names store paths. Changing any input changes the hash, which changes the path; this is how Nix guarantees no cache collisions.
- **Fixed-output derivation (FOD)**: a derivation whose output hash you declare up front (e.g. source tarballs, `fetchurl`). Allowed network access during build; everything else is sandboxed offline.
- Nix store: `/nix/store`. Immutable, content-addressed directory where *everything* Nix builds lives. Each entry is prefixed by a hash of its inputs.
- Store path: a single entry under `/nix/store`, e.g. `/nix/store/abc123…-hello-2.12.1`. The hash makes it unique per set of build inputs.
- Derivation (`.drv`): a *recipe* for a build, covering inputs, builder script, env vars, and outputs. Produced by evaluating Nix code. Not the built artifact itself.
- Realisation, or "realising a derivation": actually executing the `.drv` to produce the output store path(s). "Build" colloquially means this.
- Output path: the store path a derivation produces. A derivation can have multiple outputs (e.g. `out`, `dev`, `man`, `lib`).
- Closure: a store path plus all its runtime dependencies, transitively. What you need to copy to another machine to actually use something.
- Hash: content/input hash that names store paths. Changing any input changes the hash, which changes the path; this is how Nix guarantees no cache collisions.
- Fixed-output derivation (FOD): a derivation whose output hash you declare up front (e.g. source tarballs, `fetchurl`). Allowed network access during build; everything else is sandboxed offline.
## Language
- **Nix (the language)**: lazy, purely functional, dynamically typed. Every `.nix` file is one expression.
- **Attrset**: `{ a = 1; b = "x"; }`. The main data structure.
- **Lambda**: `x: x + 1` or `{ a, b }: a + b`. Functions take one argument; multi-arg functions are attrset-destructured.
- **`let ... in`**: local bindings, for example `let x = 1; in x + 1`.
- **`with expr; body`**: brings `expr`'s attrs into scope for `body`. Common in `with pkgs; [ hello jq ]`. Useful but can shadow bindings, so use sparingly.
- **`inherit`**: shorthand to pull names from outer scope into an attrset: `{ inherit pkgs system; }``{ pkgs = pkgs; system = system; }`.
- **`import`**: evaluate another `.nix` file. `import ./foo.nix { }` imports and calls it.
- **`callPackage`**: a nixpkgs convention that auto-supplies function arguments from a package set. You'll see it everywhere in nixpkgs.
- Nix (the language): lazy, purely functional, dynamically typed. Every `.nix` file is one expression.
- Attrset: `{ a = 1; b = "x"; }`. The main data structure.
- Lambda: `x: x + 1` or `{ a, b }: a + b`. Functions take one argument; multi-arg functions are attrset-destructured.
- `let ... in`: local bindings, for example `let x = 1; in x + 1`.
- `with expr; body`: brings `expr`'s attrs into scope for `body`. Common in `with pkgs; [ hello jq ]`. Useful but can shadow bindings, so use sparingly.
- `inherit`: shorthand to pull names from outer scope into an attrset: `{ inherit pkgs system; }``{ pkgs = pkgs; system = system; }`.
- `import`: evaluate another `.nix` file. `import ./foo.nix { }` imports and calls it.
- `callPackage`: a nixpkgs convention that auto-supplies function arguments from a package set. You'll see it everywhere in nixpkgs.
## Package Management
- **nixpkgs**: the community-maintained collection of Nix expressions for ~100k packages. Lives at `github:NixOS/nixpkgs`.
- **Channel**: a named, periodically-updated snapshot of nixpkgs (e.g. `nixos-unstable`, `nixos-25.11`). Pre-flakes way to pin. Flakes mostly replace this with lockfiles.
- **Overlay**: a function that extends or overrides a package set. Lets you patch, pin, or add packages without forking nixpkgs.
- **Profile**: a symlink tree representing "what's installed" for a user or system. `nix-env`, `nix profile`, NixOS, and home-manager all manage profiles.
- **Generation**: a versioned snapshot of a profile. Every change creates a new generation; old ones stay until garbage-collected. This is how rollback works.
- **Garbage collection (`nix-collect-garbage`)**: deletes store paths not reachable from any "GC root" (profiles, running processes, and `result` symlinks).
- nixpkgs: the community-maintained collection of Nix expressions for ~100k packages. Lives at `github:NixOS/nixpkgs`.
- Channel: a named, periodically-updated snapshot of nixpkgs (e.g. `nixos-unstable`, `nixos-25.11`). Pre-flakes way to pin. Flakes mostly replace this with lockfiles.
- Overlay: a function that extends or overrides a package set. Lets you patch, pin, or add packages without forking nixpkgs.
- Profile: a symlink tree representing "what's installed" for a user or system. `nix-env`, `nix profile`, NixOS, and home-manager all manage profiles.
- Generation: a versioned snapshot of a profile. Every change creates a new generation; old ones stay until garbage-collected. This is how rollback works.
- Garbage collection (`nix-collect-garbage`): deletes store paths not reachable from any "GC root" (profiles, running processes, and `result` symlinks).
## Flakes
- **Flake**: a directory with a `flake.nix`. Has pinned `inputs` and structured `outputs`. Reproducible, composable, and schema-driven.
- **`flake.lock`**: JSON file pinning the exact revision (and hash) of each input. Commit it.
- **Flake reference**: URL-like string identifying a flake: `github:NixOS/nixpkgs`, `path:./foo`, `git+https://…`, or `nixpkgs` (registry alias).
- **Registry**: short-name to flake-ref mapping. `nixpkgs` resolves via the global registry by default.
- **Pure evaluation**: flakes evaluate in a sandbox with no env vars, no arbitrary filesystem reads, and only declared inputs. This is what makes them reproducible.
- Flake: a directory with a `flake.nix`. Has pinned `inputs` and structured `outputs`. Reproducible, composable, and schema-driven.
- `flake.lock`: JSON file pinning the exact revision (and hash) of each input. Commit it.
- Flake reference: URL-like string identifying a flake: `github:NixOS/nixpkgs`, `path:./foo`, `git+https://…`, or `nixpkgs` (registry alias).
- Registry: short-name to flake-ref mapping. `nixpkgs` resolves via the global registry by default.
- Pure evaluation: flakes evaluate in a sandbox with no env vars, no arbitrary filesystem reads, and only declared inputs. This is what makes them reproducible.
## NixOS and home-manager
- **NixOS module**: a function `{ config, lib, pkgs, ... }: { options = …; config = …; }`. The unit of NixOS configuration.
- **home-manager**: user-level declarative config for dotfiles, per-user packages, and services. Works standalone or as a NixOS module.
- NixOS module: a function `{ config, lib, pkgs, ... }: { options = …; config = …; }`. The unit of NixOS configuration.
- home-manager: user-level declarative config for dotfiles, per-user packages, and services. Works standalone or as a NixOS module.
## CLI: Old vs. New

View File

@ -1,9 +1,9 @@
# Nix Primer
Nix is **two things bundled**:
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.
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.
@ -20,8 +20,8 @@ Every `nix build` or `nix develop` runs in two phases:
(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`.
- 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, and undefined names.
@ -107,7 +107,7 @@ 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).
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:
@ -129,10 +129,10 @@ pkgs.stdenv.mkDerivation {
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.
- `$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.
---
@ -140,13 +140,13 @@ Key ideas:
Traditional package managers put files in `/usr/bin`, `/usr/lib`, and similar shared locations. One version at a time. Upgrades mutate shared state.
Nix puts every build in `/nix/store/<hash>-<name>/` with the hash derived from **all its inputs** (recursively). That gives you:
Nix puts every build in `/nix/store/<hash>-<name>/` with the hash derived from all its inputs (recursively). That gives you:
- **Atomic upgrades and rollbacks**: switching "versions" is just switching symlinks.
- **Multiple versions coexisting**: they live at different hashes.
- **Reproducibility**: same inputs produce the same hash and the 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.
- Atomic upgrades and rollbacks: switching "versions" is just switching symlinks.
- Multiple versions coexisting: they live at different hashes.
- Reproducibility: same inputs produce the same hash and the 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.
---
@ -154,12 +154,12 @@ Nix puts every build in `/nix/store/<hash>-<name>/` with the hash derived from *
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, and ISOs).
5. **NixOS**: declare your whole OS config (packages, services, users, and network) in Nix.
6. **home-manager**: same idea, at the user level.
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, and ISOs).
5. NixOS: declare your whole OS config (packages, services, users, and 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.
@ -189,12 +189,12 @@ 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.
- 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.
---

View File

@ -1,11 +1,11 @@
# Flakes
A **flake** is a directory containing `flake.nix` (and, once evaluated, `flake.lock`). It's a packaging convention on top of regular Nix that gives you:
A flake is a directory containing `flake.nix` (and, once evaluated, `flake.lock`). It's a packaging convention on top of regular Nix that gives you:
- **Pinned inputs** via `flake.lock` for reproducible builds.
- **A fixed output schema** so tools know where to look (`devShells.<system>.default`, `packages.<system>.default`, and so on).
- **Composability**, since flakes depend on other flakes by URL.
- **Pure evaluation**, with no silent dependence on `NIX_PATH`, env vars, or random files.
- Pinned inputs via `flake.lock` for reproducible builds.
- A fixed output schema so tools know where to look (`devShells.<system>.default`, `packages.<system>.default`, and so on).
- Composability, since flakes depend on other flakes by URL.
- Pure evaluation, with no silent dependence on `NIX_PATH`, env vars, or random files.
Flakes are still marked "experimental" but are now the way most new Nix code is written.
@ -35,7 +35,7 @@ Two top-level keys matter: `inputs` and `outputs`. `description` is cosmetic.
### Inputs
Each input has a **flake reference** URL. Common forms:
Each input has a flake reference URL. Common forms:
```
github:owner/repo # latest default branch
@ -81,10 +81,10 @@ Default attrs `default` are picked when you omit the name: `nix build` ≡ `nix
`flake.lock` pins the exact commit and NAR hash of every input, transitively. Generated or updated automatically on first use or via `nix flake update`.
- **Commit it.** Without it, "my flake" means different things on different machines.
- **Update explicitly.** `nix flake update` (all inputs) or `nix flake update nixpkgs` (one input).
- **Inspect.** `nix flake metadata` prints the resolved URLs and revisions.
- **`follows`** deduplicates: if both `flake-utils` and `foo` want their own `nixpkgs`, `follows = "nixpkgs";` forces them to use yours. Without this, you can end up with multiple nixpkgs copies and subtle version skew.
- Commit it. Without it, "my flake" means different things on different machines.
- Update explicitly. `nix flake update` (all inputs) or `nix flake update nixpkgs` (one input).
- Inspect. `nix flake metadata` prints the resolved URLs and revisions.
- `follows` deduplicates: if both `flake-utils` and `foo` want their own `nixpkgs`, `follows = "nixpkgs";` forces them to use yours. Without this, you can end up with multiple nixpkgs copies and subtle version skew.
---
@ -149,9 +149,9 @@ Flakes evaluate in pure mode:
- No reading `NIX_PATH` or `<nixpkgs>`.
- No arbitrary `builtins.getEnv` or reading files outside the flake.
- `builtins.currentTime` and `builtins.currentSystem` are blocked.
- Source is pulled from the flake's input tree, not the working directory, unless you're in `self`. Files not tracked by git are **invisible** to the flake by default.
- Source is pulled from the flake's input tree, not the working directory, unless you're in `self`. Files not tracked by git are invisible to the flake by default.
This last point surprises people: **`git add` a file before `nix build` can see it.** (`nix build --impure` escapes this, but then you're not reproducible.)
This last point surprises people: `git add` a file before `nix build` can see it. (`nix build --impure` escapes this, but then you're not reproducible.)
In exchange: anyone with your `flake.nix` plus `flake.lock` gets bit-identical evaluation.
@ -223,11 +223,11 @@ overlays.default = final: prev: {
## 8. Debugging Tips
- **`nix flake show --all-systems`** to see outputs for every platform, not just yours.
- **`nix eval .#packages.x86_64-linux.default.drvPath`** to get the `.drv` without building.
- **`nix eval --json .#devShells.x86_64-linux.default.buildInputs | jq`** to inspect shell deps.
- **`--show-trace`** on any command for full eval backtraces.
- **`nix repl .`** loads your flake's outputs into a REPL: useful for poking at attrs.
- `nix flake show --all-systems` to see outputs for every platform, not just yours.
- `nix eval .#packages.x86_64-linux.default.drvPath` to get the `.drv` without building.
- `nix eval --json .#devShells.x86_64-linux.default.buildInputs | jq` to inspect shell deps.
- `--show-trace` on any command for full eval backtraces.
- `nix repl .` loads your flake's outputs into a REPL: useful for poking at attrs.
- If an input seems stuck on an old rev, check `flake.lock`; `nix flake update <name>` fixes it.
---
@ -238,4 +238,4 @@ overlays.default = final: prev: {
- A tiny personal script: plain `default.nix` plus `nix-build` is still fine.
- Large monorepos with complex CI: consider `flake-parts` or hybrid setups (flake for the interface, regular Nix for guts).
Flakes are a **packaging interface**, not a requirement. The old non-flake world still works; flakes just compose better once you have more than one Nix project.
Flakes are a packaging interface, not a requirement. The old non-flake world still works; flakes just compose better once you have more than one Nix project.