254 lines
9.1 KiB
Markdown
254 lines
9.1 KiB
Markdown
# 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`)
|
|
```
|
|
|
|
Non-flake sources (a repo without `flake.nix`):
|
|
|
|
```nix
|
|
inputs.some-src = { url = "github:owner/repo"; flake = false; };
|
|
# Access raw files via self.inputs.some-src
|
|
```
|
|
|
|
### Outputs
|
|
|
|
A function: `{ self, ...inputs }: <attrset>`. The attrset keys are *conventions* the `nix` CLI knows about:
|
|
|
|
| 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>` |
|
|
|
|
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`.
|
|
|
|
---
|
|
|
|
## 2. The Lockfile
|
|
|
|
`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.
|
|
|
|
---
|
|
|
|
## 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 ]; };
|
|
});
|
|
```
|
|
|
|
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
|
|
|
|
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.
|
|
- 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` 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
|
|
|
|
```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
|
|
|
|
```nix
|
|
packages.default = pkgs.rustPlatform.buildRustPackage {
|
|
pname = "my-tool";
|
|
version = "0.1.0";
|
|
src = ./.;
|
|
cargoLock.lockFile = ./Cargo.lock;
|
|
};
|
|
```
|
|
|
|
### Expose a NixOS Module
|
|
|
|
```nix
|
|
nixosModules.default = import ./module.nix;
|
|
# Consumer does: imports = [ inputs.my-flake.nixosModules.default ];
|
|
```
|
|
|
|
### NixOS System Config as a Flake
|
|
|
|
```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
|
|
|
|
```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).
|
|
|
|
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.
|