Build and Deploy

1. Build commands

cd ~/dotfiles
nix flake check                                                      # lint the whole flake
nix build .#nixosConfigurations.<host>.config.system.build.toplevel   # build without root
sudo nixos-rebuild switch --flake .#<host>                            # apply

The first two need no root and no target machine. Run them before touching anything.

1.1. Building as a user, activating as root

On a host where the flake checkout lives in a user's home, sudo nixos-rebuild can fail to read the repository — the version-control library refuses a repository owned by another user. Splitting the two halves works:

nix build .#nixosConfigurations.<host>.config.system.build.toplevel
sudo nix-env -p /nix/var/nix/profiles/system --set ./result
sudo ./result/bin/switch-to-configuration switch

1.2. Proving a change is a no-op

For a refactor that should change nothing, compare derivation paths. This is pure evaluation — no building, no root, and it works for hosts other than the one you are sitting at:

nix eval --raw .#nixosConfigurations.<host>.config.system.build.toplevel.drvPath

Identical before and after means provably inert. This is the right tool for namespace renames, module moves and formatting sweeps.

1.3. Do not build other hosts' closures carelessly

Building a graphical host's toplevel on a small or virtualised machine can source-compile large packages that are not in a binary cache, exhaust memory and take the environment down with it. Use the drvPath comparison above when all you need is a comparison.

2. Updating

NixOS has no apt upgrade. Updates are driven by moving the flake's input pins.

nix flake update                                   # bump ALL inputs
sudo nixos-rebuild switch --flake .#<host>

Everything pinned by the flake moves together — the language toolchains, the editor and its packages, the compositor. A new generation is created, so the blast radius of a bad update is one rollback.

To move a single input:

nix flake lock --update-input <input>
sudo nixos-rebuild switch --flake .#<host>

3. Rolling back

sudo nixos-rebuild switch --rollback

Every rebuild creates a generation and every generation is a target. The bootloader also lists them, which is the escape hatch when a change breaks the graphical session and you cannot get a shell.

3.1. What rollback does not cover

Rollback restores the system closure. It does not restore anything the system does not own:

  • Files symlinked out of the store for live editing are not generation-pinned — a rollback leaves them at whatever the working tree holds. See emanix.src.liveElisp in Options.
  • Data is data. Rollback is not a backup.
  • Anything a service mutated at runtime — a database, a cache — stays mutated.

4. Garbage collection

Collection runs daily, deleting generations older than two weeks, and the store is auto-optimised.

There is also a mid-build emergency valve: when free space falls below a floor, Nix collects during the build until a ceiling is available again. That valve is the real safety net — a timer only helps between builds, and it is during a large build that a machine actually runs out of room.

Manual collection, when needed:

sudo nix-collect-garbage -d          # delete old generations, then collect
nix store gc                         # collect only what nothing references

4.1. On a virtualised host

A virtual disk image is a high-water mark: it grows and does not shrink when files are deleted inside the guest. Collecting garbage frees space to the guest but not to the host, so image growth needs a separate, deliberate compaction step. Plan for it rather than being surprised by it.

5. Formatting and linting

nix fmt                              # format the tree
nix develop                          # shell with the language server and linters

The dev shell carries a Nix language server, the formatter, a dead-code finder and a linter — the same tools CI would run, available before you commit.