nix-playgraound/notes/003-flakes.md

265 lines
9.6 KiB
Markdown
Raw Normal View History

2026-04-15 11:49:36 +02:00
# 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:
- 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.
---
## 1. Anatomy of a `flake.nix`
```nix
{
description = "…"; # optional human-readable summary
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
# Pattern: make a transitive input follow yours, to avoid duplicates
flake-utils.url = "github:numtide/flake-utils";
flake-utils.inputs.nixpkgs.follows = "nixpkgs";
};
outputs = { self, nixpkgs, flake-utils, ... }: {
# arbitrary attrs here, but names with meaning to `nix` CLI must match the schema
};
}
```
Two top-level keys matter: `inputs` and `outputs`. `description` is cosmetic.
### Inputs
Each input has a flake reference URL. Common forms:
```
github:owner/repo # latest default branch
github:owner/repo/branch-or-tag # specific ref
github:owner/repo/abc123 # specific commit
git+https://example.com/repo.git
git+ssh://git@example.com/repo
path:./local-flake # local path
tarball+https://…/src.tar.gz
nixpkgs # registry alias (resolves via `nix registry`)
```
2026-05-04 11:29:17 +02:00
This repository now has focused examples for several of those cases:
2026-05-04 11:29:17 +02:00
- `39-flake-apps/` covers `apps.<system>` outputs,
- `40-path-inputs/` covers a local flake `path:` input, and
- `41-non-flake-inputs/` covers a local `path:` input with `flake = false`.
2026-04-15 11:49:36 +02:00
Non-flake sources (a repo without `flake.nix`):
2026-04-21 14:19:00 +02:00
2026-04-15 11:49:36 +02:00
```nix
inputs.some-src = { url = "github:owner/repo"; flake = false; };
# Access raw files via self.inputs.some-src
```
2026-05-04 11:29:17 +02:00
`41-non-flake-inputs/` shows that pattern with a local source tree and a JSON file.
2026-04-15 11:49:36 +02:00
### Outputs
A function: `{ self, ...inputs }: <attrset>`. The attrset keys are *conventions* the `nix` CLI knows about:
2026-04-21 14:19:00 +02:00
| Output path | What it is | Command |
|------------------------------|--------------------------------------|-----------------------------------------|
| `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>` |
| `nixosModules.<name>` | reusable NixOS module | imported by other flakes |
| `overlays.<name>` | nixpkgs overlay | imported by other flakes |
| `formatter.<system>` | a formatter derivation | `nix fmt` |
| `checks.<system>.<name>` | derivations run by `nix flake check` | `nix flake check` |
| `templates.<name>` | scaffold for `nix flake init` | `nix flake init -t .#<name>` |
2026-04-15 11:49:36 +02:00
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`, or `x86_64-darwin`.
2026-05-04 11:29:17 +02:00
Arbitrary attrs are still allowed alongside those schema keys. `lib` is a common convention for pure helper functions and data; `42-lib-outputs/`
shows one focused example.
2026-04-15 11:49:36 +02:00
---
## 2. The Lockfile
2026-04-21 14:19:00 +02:00
`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`.
2026-04-15 11:49:36 +02:00
- 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.
2026-04-21 14:19:00 +02:00
- `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.
2026-04-15 11:49:36 +02:00
---
## 3. Multi-System Support
A flake output has to enumerate systems explicitly. Two common patterns:
### (a) Manual `forAllSystems`
```nix
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" "x86_64-darwin" ];
forAllSystems = f:
nixpkgs.lib.genAttrs systems (system: f (import nixpkgs { inherit system; }));
in {
packages = forAllSystems (pkgs: { default = pkgs.hello; });
devShells = forAllSystems (pkgs: { default = pkgs.mkShell { packages = [ pkgs.jq ]; }; });
};
```
Zero external deps, explicit.
### (b) `flake-utils` / `flake-parts`
```nix
inputs.flake-utils.url = "github:numtide/flake-utils";
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; }; in {
packages.default = pkgs.hello;
devShells.default = pkgs.mkShell { packages = [ pkgs.jq ]; };
});
```
2026-04-21 14:19:00 +02:00
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.
2026-04-15 11:49:36 +02:00
---
## 4. Flake URIs: `.#<name>` Syntax
Everywhere you point `nix` at a flake, the form is `<flake-ref>#<attr>`:
```bash
nix build . # current dir, packages.<sys>.default
nix build .#foo # packages.<sys>.foo
nix build github:owner/repo#foo # remote flake
nix develop .#ci # devShells.<sys>.ci
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.
---
## 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` and `builtins.currentSystem` are blocked.
2026-04-21 14:19:00 +02:00
- 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.
2026-04-15 11:49:36 +02:00
2026-04-21 14:19:00 +02:00
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.)
2026-04-15 11:49:36 +02:00
In exchange: anyone with your `flake.nix` plus `flake.lock` gets bit-identical evaluation.
---
## 6. Common Commands
```bash
nix flake init # scaffold a flake.nix in current dir
nix flake init -t github:nix-community/templates#rust
nix flake show # tree of all outputs
nix flake metadata # locked inputs + paths
nix flake check # evaluate & build all `checks` outputs
nix flake update # bump all inputs
nix flake update nixpkgs # bump one input
nix flake lock --override-input nixpkgs github:NixOS/nixpkgs/nixos-24.11
nix develop # enter devShells.<sys>.default
nix develop .#ci # named shell
nix build # build packages.<sys>.default → ./result
nix run # run apps.<sys>.default (or packages.<sys>.default)
```
---
## 7. Recurring Patterns Worth Recognizing
### Rust / Go / Python Dev Shell
2026-04-21 14:19:00 +02:00
2026-04-15 11:49:36 +02:00
```nix
devShells.default = pkgs.mkShell {
packages = [ pkgs.cargo pkgs.rustc pkgs.rust-analyzer ];
env.RUST_LOG = "debug";
shellHook = ''export FOO=bar'';
};
```
### Package a Local Source Tree
2026-04-21 14:19:00 +02:00
2026-04-15 11:49:36 +02:00
```nix
packages.default = pkgs.rustPlatform.buildRustPackage {
pname = "my-tool";
version = "0.1.0";
src = ./.;
cargoLock.lockFile = ./Cargo.lock;
};
```
### Expose a NixOS Module
2026-04-21 14:19:00 +02:00
2026-04-15 11:49:36 +02:00
```nix
nixosModules.default = import ./module.nix;
# Consumer does: imports = [ inputs.my-flake.nixosModules.default ];
```
### NixOS System Config as a Flake
2026-04-21 14:19:00 +02:00
2026-04-15 11:49:36 +02:00
```nix
nixosConfigurations.my-laptop = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [ ./configuration.nix ];
};
# Apply with: nixos-rebuild switch --flake .#my-laptop
```
### Overlay as an Output
2026-04-21 14:19:00 +02:00
2026-04-15 11:49:36 +02:00
```nix
overlays.default = final: prev: {
my-patched-foo = prev.foo.overrideAttrs (old: { … });
};
```
---
## 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.
- If an input seems stuck on an old rev, check `flake.lock`; `nix flake update <name>` fixes it.
---
## 9. When Flakes Are Overkill
- 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).
2026-04-21 14:19:00 +02:00
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.