This commit is contained in:
Hassan Abedi 2026-04-15 13:44:59 +02:00
parent 35402cccda
commit d2814acdc9
4 changed files with 98 additions and 98 deletions

View File

@ -1,8 +1,8 @@
{
description = "A minimal dev shell your first flake";
description = "A minimal dev shell: your first flake";
# Inputs: other flakes this one depends on.
# `nixpkgs` is the big package set. `follows` isn't used here, but you'll
# `nixpkgs` is the main package set. `follows` isn't used here, but you'll
# see it a lot when composing flakes (e.g. `inputs.foo.inputs.nixpkgs.follows = "nixpkgs";`).
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";

View File

@ -1,52 +1,52 @@
# Glossary
Core vocabulary you'll hit in the first hour of Nix. Skim, don't memorize come back as needed.
Core vocabulary you'll hit in the first hour of Nix. Skim, don't memorize; come back as needed.
## The store and builds
## 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: inputs, builder script, env vars, 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 / "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: `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 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
## Package Management
- **nixpkgs** — the big 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/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, `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, 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://…`, `nixpkgs` (registry alias).
- **Registry** — short-name → flake-ref mapping. `nixpkgs` resolves via the global registry by default.
- **Pure evaluation** — flakes evaluate in a sandbox: no env vars, no arbitrary filesystem reads, 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 & home-manager
## NixOS and home-manager
- **NixOS module** a function `{ config, lib, pkgs, ... }: { options = …; config = …; }`. The unit of NixOS configuration.
- **home-manager** — user-level declarative config (dotfiles, per-user packages, 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
## CLI: Old vs. New
| Old (pre-flakes) | New (flakes) |
|---|---|

View File

@ -9,9 +9,9 @@ Confusingly, both are called "Nix." Separate them in your head and everything ge
---
## 1. The mental model: eval, then build
## 1. The Mental Model: Eval, Then Build
Every `nix build` / `nix develop` runs in two phases:
Every `nix build` or `nix develop` runs in two phases:
```
.nix source ──(evaluate)──▶ .drv file ──(realise)──▶ /nix/store/...-output
@ -23,13 +23,13 @@ Every `nix build` / `nix develop` runs in two phases:
- **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.
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.
---
## 2. The language in 5 minutes
## 2. The Language in 5 Minutes
### Primitives and collections
### Primitives and Collections
```nix
42 # integer
@ -40,7 +40,7 @@ You can evaluate without building (`nix eval`, `nix-instantiate`) and you can bu
true false null
[ 1 2 3 ] # list (space-separated, NOT comma)
{ a = 1; b = "two"; } # attrset
/foo/bar # path (literal not a string!)
/foo/bar # path (literal, not a string!)
./relative/path # path relative to current file
```
@ -69,7 +69,7 @@ The `...` means "ignore extra attrs." Without it, passing extras is an error.
```nix
let
x = 1;
y = x + 1; # can reference earlier bindings (and each other lazy)
y = x + 1; # can reference earlier bindings (and each other: lazy)
in x + y
```
@ -78,7 +78,7 @@ in x + y
{ inherit (pkgs) hello jq; } # pulls hello/jq from pkgs into this attrset
```
### `with` (use cautiously)
### `with` (Use Cautiously)
```nix
with pkgs; [ hello jq ripgrep ]
@ -86,11 +86,11 @@ 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.
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.
Nothing is evaluated until needed. This is why you can have large 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`
@ -105,7 +105,7 @@ import ./foo.nix { x = 1; } # if foo.nix is a function, also call it
---
## 3. Derivations — the core primitive
## 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).
@ -117,7 +117,7 @@ Minimal example:
pkgs.stdenv.mkDerivation {
pname = "hello-note";
version = "0.1";
src = ./.; # path gets copied into the store
src = ./.; # path: gets copied into the store
installPhase = ''
mkdir -p $out/bin
echo '#!/bin/sh' > $out/bin/hello-note
@ -132,40 +132,40 @@ 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.
- **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
## 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.
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:
- **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.
- **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.
---
## 5. How you'll actually use Nix day-to-day
## 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.
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.
---
## 6. Minimum useful commands
## 6. Minimum Useful Commands
```bash
# Language / introspection
@ -187,7 +187,7 @@ nix why-depends ./result nixpkgs#glibc # trace dependency chains
---
## 7. Gotchas that trip up newcomers
## 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.
@ -198,8 +198,8 @@ nix why-depends ./result nixpkgs#glibc # trace dependency chains
---
## 8. What to read/try next
## 8. What to Read or Try Next
- Open `nix repl`, type `:l <nixpkgs>`, then `hello`, `hello.drv`, `hello.outPath`. Poke around.
- Read [`nix.dev`](https://nix.dev) the current best introduction.
- 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.

View File

@ -2,12 +2,12 @@
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` reproducible builds.
- **A fixed output schema** tools know where to look (`devShells.<system>.default`, `packages.<system>.default`, etc.).
- **Composability** flakes depend on other flakes by URL.
- **Pure evaluation** 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 overwhelmingly the way new Nix code is written.
Flakes are still marked "experimental" but are now the way most new Nix code is written.
---
@ -26,7 +26,7 @@ Flakes are still marked "experimental" but are overwhelmingly the way new Nix co
};
outputs = { self, nixpkgs, flake-utils, ... }: {
# arbitrary attrs here but names with meaning to `nix` CLI must match the schema
# arbitrary attrs here, but names with meaning to `nix` CLI must match the schema
};
}
```
@ -60,8 +60,8 @@ A function: `{ self, ...inputs }: <attrset>`. The attrset keys are *conventions*
| Output path | What it is | Command |
|---|---|---|
| `devShells.<system>.<name>` | A `mkShell` dev environment | `nix develop [.#<name>]` |
| `packages.<system>.<name>` | A derivation build target | `nix build [.#<name>]` |
| `devShells.<system>.<name>` | A `mkShell`, a dev environment | `nix develop [.#<name>]` |
| `packages.<system>.<name>` | A derivation, a build target | `nix build [.#<name>]` |
| `apps.<system>.<name>` | `{ type = "app"; program = …; }` | `nix run [.#<name>]` |
| `nixosConfigurations.<host>` | `nixpkgs.lib.nixosSystem { … }` | `nixos-rebuild switch --flake .#<host>` |
| `homeConfigurations.<user>` | home-manager config | `home-manager switch --flake .#<user>` |
@ -73,13 +73,13 @@ A function: `{ self, ...inputs }: <attrset>`. The attrset keys are *conventions*
Default attrs `default` are picked when you omit the name: `nix build``nix build .#default`.
`.<system>` is the platform triple: `x86_64-linux`, `aarch64-linux`, `aarch64-darwin`, `x86_64-darwin`.
`.<system>` is the platform triple: `x86_64-linux`, `aarch64-linux`, `aarch64-darwin`, or `x86_64-darwin`.
---
## 2. The lockfile
## 2. The Lockfile
`flake.lock` pins the exact commit + NAR hash of every input, transitively. Generated/updated automatically on first use or `nix flake update`.
`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).
@ -88,7 +88,7 @@ Default attrs `default` are picked when you omit the name: `nix build` ≡ `nix
---
## 3. Multi-system support
## 3. Multi-System Support
A flake output has to enumerate systems explicitly. Two common patterns:
@ -121,11 +121,11 @@ outputs = { self, nixpkgs, flake-utils }:
});
```
Less boilerplate, but adds a dependency. For bigger projects, `flake-parts` is increasingly popular — it gives you a module system for flakes themselves.
Less boilerplate, at the cost of one dependency. For larger projects, `flake-parts` is increasingly popular: it gives you a module system for flakes themselves.
---
## 4. Flake URIs: `.#<name>` syntax
## 4. Flake URIs: `.#<name>` Syntax
Everywhere you point `nix` at a flake, the form is `<flake-ref>#<attr>`:
@ -138,26 +138,26 @@ nix run nixpkgs#hello # pkgs.hello from the nixpkgs registry flake
nixos-rebuild switch --flake .#my-host
```
The `<system>` portion is auto-inferred from your machine you don't spell it out in the CLI.
The `<system>` portion is auto-inferred from your machine; you don't spell it out in the CLI.
---
## 5. Pure evaluation — what you lose, what you gain
## 5. Pure Evaluation: What You Lose, What You Gain
Flakes evaluate in pure mode:
- No reading `NIX_PATH` or `<nixpkgs>`.
- No arbitrary `builtins.getEnv` or reading files outside the flake.
- `builtins.currentTime` / `currentSystem` are blocked.
- `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.
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` + `flake.lock` gets bit-identical evaluation.
In exchange: anyone with your `flake.nix` plus `flake.lock` gets bit-identical evaluation.
---
## 6. Common commands
## 6. Common Commands
```bash
nix flake init # scaffold a flake.nix in current dir
@ -176,9 +176,9 @@ nix run # run apps.<sys>.default (or packages.<sys>.d
---
## 7. Recurring patterns worth recognizing
## 7. Recurring Patterns Worth Recognizing
### Rust / Go / Python dev shell
### Rust / Go / Python Dev Shell
```nix
devShells.default = pkgs.mkShell {
packages = [ pkgs.cargo pkgs.rustc pkgs.rust-analyzer ];
@ -187,7 +187,7 @@ devShells.default = pkgs.mkShell {
};
```
### Package a local source tree
### Package a Local Source Tree
```nix
packages.default = pkgs.rustPlatform.buildRustPackage {
pname = "my-tool";
@ -197,13 +197,13 @@ packages.default = pkgs.rustPlatform.buildRustPackage {
};
```
### Expose a NixOS module
### Expose a NixOS Module
```nix
nixosModules.default = import ./module.nix;
# Consumer does: imports = [ inputs.my-flake.nixosModules.default ];
```
### NixOS system config as a flake
### NixOS System Config as a Flake
```nix
nixosConfigurations.my-laptop = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
@ -212,7 +212,7 @@ nixosConfigurations.my-laptop = nixpkgs.lib.nixosSystem {
# Apply with: nixos-rebuild switch --flake .#my-laptop
```
### Overlay as an output
### Overlay as an Output
```nix
overlays.default = final: prev: {
my-patched-foo = prev.foo.overrideAttrs (old: { … });
@ -221,21 +221,21 @@ overlays.default = final: prev: {
---
## 8. Debugging tips
## 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 — great for poking at attrs.
- **`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.
---
## 9. When flakes are overkill
## 9. When Flakes Are Overkill
- Running a one-off tool: `nix run nixpkgs#ripgrep` no flake needed.
- A tiny personal script: plain `default.nix` + `nix-build` is still fine.
- Running a one-off tool: `nix run nixpkgs#ripgrep`, no flake needed.
- 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.