← Tech Guides
01

Quick Reference

The most-used Nix commands at a glance. All commands assume the new nix CLI (flakes-enabled).

Essential Commands

Run a package without installing

# Run a program from nixpkgs
nix run nixpkgs#cowsay -- "Hello Nix"

# Run a specific package version
nix run nixpkgs/nixos-24.11#python3

Enter a dev shell

# From a flake in the current directory
nix develop

# Ad-hoc shell with packages
nix shell nixpkgs#nodejs nixpkgs#yarn

Build a derivation

# Build the default package
nix build

# Build a specific output
nix build .#myPackage
ls -la ./result

Search packages

# Search nixpkgs
nix search nixpkgs ripgrep

# Search with regex
nix search nixpkgs 'python3.*Packages.flask'

System management (NixOS)

# Rebuild and switch
sudo nixos-rebuild switch --flake .#myhost

# Test without committing
sudo nixos-rebuild test --flake .#myhost

Garbage collection

# Delete old generations
nix-collect-garbage -d

# Delete generations older than 30 days
nix-collect-garbage --delete-older-than 30d

Installation

# Multi-user install (recommended, Linux & macOS)
sh <(curl -L https://nixos.org/nix/install) --daemon

# Enable flakes & new CLI (add to ~/.config/nix/nix.conf)
experimental-features = nix-command flakes

# Verify installation
nix --version
nix doctor
Tip: The Determinate Systems installer (curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install) enables flakes by default and works well on both Linux and macOS.
02

Nix Language

A lazy, pure, functional expression language. Everything evaluates to a value — there are no statements.

Primitives & Basic Types

# Strings
"hello world"
''
  Multi-line strings (indentation is stripped).
  Useful for scripts and configs.
''
"Interpolation: ${pkgName}-${version}"

# Numbers
42
3.14

# Booleans & null
true  false  null

# Paths (resolved relative to the file)
./src/main.rs        # relative path
/etc/nixos           # absolute path
<nixpkgs>            # search path (NIX_PATH)

Attribute Sets (attrsets)

The fundamental data structure — think JSON objects or Python dicts.

# Basic attrset
{
  name = "my-package";
  version = "1.0.0";
  meta = {
    license = "MIT";
  };
}

# Recursive attrset (self-referencing)
rec {
  x = 1;
  y = x + 1;  # y = 2
}

# Access attributes
mySet.name            # "my-package"
mySet.meta.license    # "MIT"
mySet.missing or "default"  # with fallback

# Merge attrsets
{ a = 1; } // { b = 2; }  # { a = 1; b = 2; }
{ a = 1; } // { a = 9; }  # { a = 9; } (right wins)

# Check membership
mySet ? name           # true

let / in & with

# let bindings introduce local scope
let
  name = "hello";
  version = "2.12";
in
  "${name}-${version}"  # "hello-2.12"

# with brings an attrset's attrs into scope
with pkgs; [
  git
  ripgrep
  fd
]
# equivalent to [ pkgs.git pkgs.ripgrep pkgs.fd ]

Functions

# Single-argument function (lambda)
x: x + 1

# Multi-argument (curried)
x: y: x + y

# Attrset pattern (destructuring)
{ name, version, ... }:
  "${name}-${version}"

# With default values
{ name, version ? "0.0.0", ... }:
  "${name}-${version}"

# Bind the whole set too
{ name, ... } @ args:
  args // { wrapped = true; }

# Named function (just a binding)
let
  add = a: b: a + b;
in
  add 3 4  # 7

Lists & Conditionals

# Lists (heterogeneous, whitespace-separated)
[ 1 "two" true ./path { a = 3; } ]

# Concatenation
[ 1 2 ] ++ [ 3 4 ]  # [ 1 2 3 4 ]

# Conditionals
if x > 0 then "positive" else "non-positive"

# assert (fails evaluation if false)
assert builtins.pathExists ./flake.nix;
  "flake exists"

Imports & Built-ins

# Import another .nix file (evaluates it)
import ./lib.nix

# Import with arguments
import ./package.nix { inherit pkgs; }

# Useful builtins
builtins.map (x: x * 2) [ 1 2 3 ]  # [ 2 4 6 ]
builtins.filter (x: x > 2) [ 1 2 3 4 ]  # [ 3 4 ]
builtins.attrNames { a = 1; b = 2; }  # [ "a" "b" ]
builtins.length [ 1 2 3 ]  # 3
builtins.readFile ./README.md
builtins.toJSON { hello = "world"; }
builtins.fetchurl { url = "..."; sha256 = "..."; }

# inherit (shorthand for attr = attr)
let x = 1; y = 2;
in { inherit x y; }  # { x = 1; y = 2; }

# inherit from another set
{ inherit (pkgs) git ripgrep; }
03

Nix Store & Derivations

The Nix store is an immutable, content-addressed filesystem. Every package is a unique path determined by its inputs.

Store Paths

# Anatomy of a store path
/nix/store/<hash>-<name>-<version>
/nix/store/wadmyilr...-ripgrep-14.1.0/

# The hash encodes ALL inputs:
#   source code + dependencies + build script + system arch
# Change any input → different hash → different path

# Inspect a store path
nix path-info --json nixpkgs#ripgrep
nix path-info -rsSh nixpkgs#ripgrep   # recursive size

# Show the dependency tree
nix-store -q --tree /nix/store/...-ripgrep-14.1.0

# Show immediate references
nix-store -q --references /nix/store/...-ripgrep-14.1.0

# Show what depends on a path (reverse refs)
nix-store -q --referrers /nix/store/...-glibc-2.38

Derivations

A derivation is the blueprint for building a package — it specifies all inputs, the builder script, and the expected output.

# A minimal derivation (rarely written by hand)
derivation {
  name = "hello";
  system = "x86_64-linux";
  builder = "/bin/sh";
  args = [ "-c" "echo Hello > $out" ];
}

# In practice, use mkDerivation from nixpkgs:
stdenv.mkDerivation {
  pname = "my-tool";
  version = "1.0.0";
  src = ./.;

  buildInputs = [ pkgs.openssl pkgs.zlib ];
  nativeBuildInputs = [ pkgs.pkg-config ];

  buildPhase = ''
    make -j$NIX_BUILD_CORES
  '';

  installPhase = ''
    mkdir -p $out/bin
    cp my-tool $out/bin/
  '';
}
Key insight: buildInputs are for the target platform (libraries to link against). nativeBuildInputs are for the build platform (compilers, code generators). This distinction matters for cross-compilation.

Build Phases

The standard build process in stdenv.mkDerivation follows a sequence of phases:

Phase Default Behavior Override
unpackPhase Extract src tarball or copy source Custom extraction logic
patchPhase Apply patches = [...] Inline sed / substituteInPlace
configurePhase Run ./configure --prefix=$out configureFlags, cmakeFlags
buildPhase Run make makeFlags, custom commands
checkPhase Run make check (if doCheck = true) checkPhase, checkTarget
installPhase Run make install Manual cp / install commands
fixupPhase Strip binaries, patch RPATHs dontStrip, dontPatchELF
04

Flakes

The modern interface for Nix projects. A flake is a directory with a flake.nix and a flake.lock for pinned, reproducible evaluation.

flake.nix Structure

{
  description = "My awesome project";

  # Inputs: dependencies on other flakes
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    flake-utils.url = "github:numtide/flake-utils";
    rust-overlay = {
      url = "github:oxalica/rust-overlay";
      inputs.nixpkgs.follows = "nixpkgs";  # share nixpkgs
    };
  };

  # Outputs: what this flake provides
  outputs = { self, nixpkgs, flake-utils, ... }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = import nixpkgs { inherit system; };
      in {
        # nix build
        packages.default = pkgs.callPackage ./default.nix { };

        # nix develop
        devShells.default = pkgs.mkShell {
          packages = with pkgs; [ nodejs yarn ];
        };

        # nix run
        apps.default = {
          type = "app";
          program = "${self.packages.${system}.default}/bin/my-app";
        };
      }
    );
}

Input URL Syntax

Source URL Format Example
GitHub github:owner/repo/ref github:NixOS/nixpkgs/nixos-24.11
GitLab gitlab:owner/repo/ref gitlab:rycee/nur-expressions
Git git+https://url?ref=&rev= git+https://example.com/repo.git?ref=main
Local path path:/absolute/path path:./subdir
Tarball https://url/archive.tar.gz https://releases.example.com/1.0.tar.gz
Flake registry flake:name or just name nixpkgs (resolves via registry)

Flake Lock & Updates

# Update all inputs
nix flake update

# Update a single input
nix flake update nixpkgs

# Pin to a specific commit
nix flake update --override-input nixpkgs \
  github:NixOS/nixpkgs/abc123def

# Show flake metadata
nix flake metadata

# Show flake outputs
nix flake show

# Initialize a new flake
nix flake init -t templates#full
Important: The flake.lock must be checked into version control. It pins exact revisions for all inputs, ensuring reproducibility across machines and time.

Flake Output Schema

Standard output attributes that tools know how to consume:

Output Used By Description
packages.<system>.<name> nix build Buildable derivations
devShells.<system>.<name> nix develop Development environments
apps.<system>.<name> nix run Runnable programs
nixosConfigurations.<host> nixos-rebuild Full NixOS system configs
nixosModules.<name> imports Reusable NixOS modules
homeConfigurations.<user> home-manager User-level configs
overlays.<name> nixpkgs.overlays Package set modifications
templates.<name> nix flake init Project scaffolding
checks.<system>.<name> nix flake check CI test derivations
formatter.<system> nix fmt Code formatter (e.g., alejandra)
05

NixOS Configuration

NixOS is a Linux distribution built on Nix. The entire system — kernel, services, users, packages — is declared in a single configuration.

configuration.nix (Classic)

# /etc/nixos/configuration.nix
{ config, pkgs, ... }: {

  # System basics
  system.stateVersion = "24.11";
  networking.hostName = "frost";
  time.timeZone = "America/New_York";

  # Boot
  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  # Networking
  networking.networkmanager.enable = true;
  networking.firewall = {
    enable = true;
    allowedTCPPorts = [ 22 80 443 ];
  };

  # Users
  users.users.alice = {
    isNormalUser = true;
    extraGroups = [ "wheel" "networkmanager" "docker" ];
    shell = pkgs.zsh;
  };

  # System packages
  environment.systemPackages = with pkgs; [
    vim git wget curl htop
    firefox
  ];

  # Services
  services.openssh.enable = true;
  services.tailscale.enable = true;

  # Desktop
  services.xserver.enable = true;
  services.xserver.desktopManager.gnome.enable = true;
  services.xserver.displayManager.gdm.enable = true;

  # Docker
  virtualisation.docker.enable = true;
}

Flake-Based NixOS Config

# flake.nix
{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";

  outputs = { self, nixpkgs }: {
    nixosConfigurations.frost = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        ./hardware-configuration.nix
        ./configuration.nix
        ./modules/networking.nix
        ./modules/desktop.nix
      ];
    };
  };
}

# Rebuild
# sudo nixos-rebuild switch --flake .#frost

NixOS Modules

Modules are the composable building blocks of NixOS configuration. Each module can declare options, import other modules, and contribute config values.

# modules/my-service.nix
{ config, lib, pkgs, ... }:

let
  cfg = config.services.myService;
in {

  # Declare options
  options.services.myService = {
    enable = lib.mkEnableOption "my custom service";
    port = lib.mkOption {
      type = lib.types.port;
      default = 8080;
      description = "Port to listen on";
    };
  };

  # Implement the module
  config = lib.mkIf cfg.enable {
    systemd.services.myService = {
      description = "My Custom Service";
      wantedBy = [ "multi-user.target" ];
      serviceConfig = {
        ExecStart = "${pkgs.myService}/bin/myservice --port ${toString cfg.port}";
        Restart = "always";
        DynamicUser = true;
      };
    };
    networking.firewall.allowedTCPPorts = [ cfg.port ];
  };
}

Common NixOS Options

Audio (PipeWire)

services.pipewire = {
  enable = true;
  alsa.enable = true;
  pulse.enable = true;
};

Fonts

fonts.packages = with pkgs; [
  noto-fonts
  noto-fonts-cjk-sans
  fira-code
  (nerdfonts.override {
    fonts = [ "JetBrainsMono" ];
  })
];

NVIDIA GPU

services.xserver.videoDrivers =
  [ "nvidia" ];
hardware.nvidia = {
  modesetting.enable = true;
  open = true;
};

Hyprland (Wayland)

programs.hyprland = {
  enable = true;
  xwayland.enable = true;
};
06

Home Manager

Manage user-level packages, dotfiles, and per-user services declaratively. Works standalone or as a NixOS module.

Standalone Setup (Flake)

# flake.nix
{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
    home-manager = {
      url = "github:nix-community/home-manager/release-24.11";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { nixpkgs, home-manager, ... }: {
    homeConfigurations."alice" = home-manager.lib.homeManagerConfiguration {
      pkgs = nixpkgs.legacyPackages.x86_64-linux;
      modules = [ ./home.nix ];
    };
  };
}

# Apply: home-manager switch --flake .#alice

home.nix

{ config, pkgs, ... }: {

  home.username = "alice";
  home.homeDirectory = "/home/alice";
  home.stateVersion = "24.11";

  # User packages
  home.packages = with pkgs; [
    ripgrep fd bat eza jq
    nodejs python3 rustup
  ];

  # Manage dotfiles
  home.file.".config/starship.toml".source = ./dotfiles/starship.toml;

  # Or write content directly
  home.file.".npmrc".text = ''
    prefix=~/.npm-global
    fund=false
  '';

  # Git
  programs.git = {
    enable = true;
    userName = "Alice";
    userEmail = "alice@example.com";
    extraConfig = {
      init.defaultBranch = "main";
      pull.rebase = true;
    };
    delta.enable = true;
  };

  # Zsh
  programs.zsh = {
    enable = true;
    enableCompletion = true;
    autosuggestion.enable = true;
    syntaxHighlighting.enable = true;
    shellAliases = {
      ll = "eza -la";
      cat = "bat";
    };
  };

  # Starship prompt
  programs.starship = {
    enable = true;
    enableZshIntegration = true;
  };

  # Direnv (auto-activate dev shells)
  programs.direnv = {
    enable = true;
    nix-direnv.enable = true;
  };
}

As a NixOS Module

# In your NixOS flake.nix
modules = [
  home-manager.nixosModules.home-manager
  {
    home-manager.useGlobalPkgs = true;
    home-manager.useUserPackages = true;
    home-manager.users.alice = import ./home.nix;
  }
];
Tip: useGlobalPkgs = true makes home-manager use the system-level nixpkgs instead of its own copy — faster evals and consistent package versions.
07

Dev Shells

Project-level development environments with reproducible toolchains. The Nix answer to Docker-for-development, virtualenvs, and version managers.

mkShell Basics

# In a flake's devShells output:
devShells.default = pkgs.mkShell {
  # Packages available in the shell
  packages = with pkgs; [
    nodejs_20 yarn
    python312 python312Packages.pip
    postgresql_16
    redis
  ];

  # Environment variables
  DATABASE_URL = "postgres://localhost/myapp_dev";
  NODE_ENV = "development";

  # Shell hooks (run on enter)
  shellHook = ''
    echo "Dev environment ready!"
    export PATH="$PWD/node_modules/.bin:$PATH"
  '';

  # C/C++ build dependencies
  nativeBuildInputs = with pkgs; [ pkg-config cmake ];
  buildInputs = with pkgs; [ openssl zlib ];

  # Set LIBRARY_PATH, etc.
  LD_LIBRARY_PATH = lib.makeLibraryPath [
    pkgs.openssl pkgs.zlib
  ];
};

Multiple Shells

devShells = {
  # nix develop (default)
  default = pkgs.mkShell {
    packages = with pkgs; [ nodejs yarn ];
  };

  # nix develop .#ci
  ci = pkgs.mkShell {
    packages = with pkgs; [ nodejs yarn playwright-driver ];
  };

  # nix develop .#docs
  docs = pkgs.mkShell {
    packages = with pkgs; [ mdbook ];
  };
};

Direnv Integration

Automatically activate the dev shell when you cd into the project directory.

# .envrc (in project root)
use flake

# Then allow it
direnv allow

# nix-direnv caches the shell in the Nix store
# so re-entry is instant (no re-evaluation)

# For non-flake projects
# use nix -p nodejs yarn
Best practice: Commit .envrc to the repo. Team members with direnv + nix-direnv get the dev environment automatically on cd. No onboarding docs needed.

Language-Specific Shells

Rust

pkgs.mkShell {
  packages = with pkgs; [
    rustc cargo clippy
    rustfmt rust-analyzer
  ];
  RUST_SRC_PATH =
    "${pkgs.rust.packages.stable.rustPlatform.rustLibSrc}";
}

Python

let
  py = pkgs.python312;
in pkgs.mkShell {
  packages = [
    (py.withPackages (ps: [
      ps.flask ps.requests
      ps.pytest ps.black
    ]))
  ];
}

Go

pkgs.mkShell {
  packages = with pkgs; [
    go gopls delve
    golangci-lint
  ];
  GOPATH =
    "$PWD/.go";
}

Node.js + TypeScript

pkgs.mkShell {
  packages = with pkgs; [
    nodejs_22 corepack
    nodePackages.typescript
    nodePackages.typescript-language-server
  ];
}
08

Nixpkgs

The largest package repository in the world (100,000+ packages). Understanding overlays and overrides lets you customize any package.

Overlays

Overlays modify or extend the entire nixpkgs set. They are functions that take two arguments: final (the result after all overlays) and prev (the set before this overlay).

# Overlay: add a custom package
overlays = [
  (final: prev: {
    my-tool = final.callPackage ./pkgs/my-tool.nix { };
  })
];

# Overlay: replace an existing package
(final: prev: {
  htop = prev.htop.overrideAttrs (old: {
    patches = (old.patches or []) ++ [ ./htop-custom.patch ];
  });
})

# Apply overlays when importing nixpkgs
pkgs = import nixpkgs {
  inherit system;
  overlays = [ my-overlay rust-overlay.overlays.default ];
  config.allowUnfree = true;
};

Overrides

# overrideAttrs: modify a derivation's attributes
pkgs.ripgrep.overrideAttrs (old: {
  version = "14.2.0";
  src = pkgs.fetchFromGitHub {
    owner = "BurntSushi";
    repo = "ripgrep";
    rev = "14.2.0";
    hash = "sha256-...";
  };
})

# override: change function arguments (callPackage inputs)
pkgs.my-package.override {
  openssl = pkgs.libressl;  # swap dependency
}

# Python package overrides
pkgs.python3Packages.requests.overridePythonAttrs (old: {
  doCheck = false;  # skip tests
})

# Override entire Python package set
let
  python = pkgs.python3.override {
    packageOverrides = pyFinal: pyPrev: {
      my-lib = pyFinal.callPackage ./my-lib.nix { };
    };
  };
in
  python.withPackages (ps: [ ps.my-lib ps.flask ])

Channels & Branches

Branch Use Case Updates
nixos-24.11 Stable NixOS release Security & critical fixes only
nixpkgs-unstable Latest packages (non-NixOS) Rolling, CI-tested on Linux & macOS
nixos-unstable Latest packages (NixOS) Rolling, NixOS-tested
master Development / PRs Untested, don't use directly
Pattern: Pin your main input to a stable branch, then add an unstable input for specific bleeding-edge packages:
inputs.unstable.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
09

Building Packages

Language-specific builders that handle the common patterns for each ecosystem.

Rust (buildRustPackage)

{ lib, rustPlatform, fetchFromGitHub, pkg-config, openssl }:

rustPlatform.buildRustPackage rec {
  pname = "my-rust-tool";
  version = "0.5.0";

  src = fetchFromGitHub {
    owner = "user";
    repo = pname;
    rev = "v${version}";
    hash = "sha256-AAAA...";
  };

  cargoHash = "sha256-BBBB...";

  nativeBuildInputs = [ pkg-config ];
  buildInputs = [ openssl ];

  meta = with lib; {
    description = "A cool Rust tool";
    license = licenses.mit;
    mainProgram = "my-rust-tool";
  };
}

Go (buildGoModule)

{ lib, buildGoModule, fetchFromGitHub }:

buildGoModule rec {
  pname = "my-go-tool";
  version = "1.2.0";

  src = fetchFromGitHub {
    owner = "user";
    repo = pname;
    rev = "v${version}";
    hash = "sha256-...";
  };

  vendorHash = "sha256-...";
  # Use vendorHash = null; if vendored already

  ldflags = [ "-s" "-w" "-X main.version=${version}" ];
}

Node.js & Python

buildNpmPackage

pkgs.buildNpmPackage {
  pname = "my-app";
  version = "1.0.0";
  src = ./.;
  npmDepsHash = "sha256-...";
  nodejs = pkgs.nodejs_20;
}

buildPythonPackage

pkgs.python3Packages.buildPythonPackage {
  pname = "my-lib";
  version = "0.1.0";
  src = ./.;
  format = "pyproject";
  build-system = [ pkgs.python3Packages.setuptools ];
  dependencies = [ pkgs.python3Packages.requests ];
}

Fetching Sources

# From GitHub
fetchFromGitHub {
  owner = "NixOS";
  repo = "nixpkgs";
  rev = "abc123";
  hash = "sha256-...";     # use lib.fakeHash to discover
}

# From URL
fetchurl {
  url = "https://example.com/source-1.0.tar.gz";
  hash = "sha256-...";
}

# Git repo (with submodules)
fetchgit {
  url = "https://github.com/user/repo.git";
  rev = "abc123";
  hash = "sha256-...";
  fetchSubmodules = true;
}

# Tip: Use lib.fakeHash as placeholder, build fails and shows real hash
hash = lib.fakeHash;  # "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
10

Operations

Garbage collection, profiles, generations, and rollbacks — keeping your Nix store healthy and your system recoverable.

Garbage Collection

# Delete all old generations and run GC
nix-collect-garbage -d

# Delete generations older than 14 days
nix-collect-garbage --delete-older-than 14d

# Just run GC (don't delete generations)
nix store gc

# See how much space will be freed
nix store gc --dry-run

# Optimize store (hardlink duplicate files)
nix store optimise

# Check store integrity
nix store verify --all

# Show store disk usage
du -sh /nix/store
Caution: nix-collect-garbage -d deletes all old system and user profile generations. You cannot rollback after this. Prefer --delete-older-than 30d to keep a safety window.

Profiles & Generations

# List system generations (NixOS)
sudo nix-env --list-generations -p /nix/var/nix/profiles/system

# List user profile generations
nix-env --list-generations

# Rollback to previous generation (NixOS)
sudo nixos-rebuild switch --rollback

# Switch to a specific generation
sudo nix-env --switch-generation 42 \
  -p /nix/var/nix/profiles/system
sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch

# Delete a specific generation
nix-env --delete-generations 38 39 40

# At boot: GRUB / systemd-boot shows all generations
# Select an older generation to boot into a known-good state

Binary Cache

# Default: cache.nixos.org (pre-built binaries)

# Add extra caches in nix.conf or NixOS config
nix.settings = {
  substituters = [
    "https://cache.nixos.org"
    "https://nix-community.cachix.org"
    "https://my-company.cachix.org"
  ];
  trusted-public-keys = [
    "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY="
    "nix-community.cachix.org-1:mB9FSh9qf2dCimDSUo8Zy7bkq5CX+/rkCWyvRCYg3Fs="
  ];
};

# Push to a cache (using cachix)
nix build .#myPackage | cachix push my-cache

Debugging Builds

# Build with verbose output
nix build -L .#myPackage

# Show derivation details
nix derivation show .#myPackage

# Enter a shell with build environment (without building)
nix develop .#myPackage

# Build and keep the temp directory on failure
nix build --keep-failed .#myPackage
# Check /tmp/nix-build-* for build artifacts

# Show why a package depends on something
nix why-depends nixpkgs#ripgrep nixpkgs#glibc

# Diff two derivations
nix store diff-closures /nix/var/nix/profiles/system-41 \
                        /nix/var/nix/profiles/system-42
11

nix-darwin

Declarative macOS system configuration, modeled after NixOS. Manage packages, services, defaults, and Homebrew from a single flake.

Flake Setup

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    darwin = {
      url = "github:LnL7/nix-darwin";
      inputs.nixpkgs.follows = "nixpkgs";
    };
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { self, darwin, nixpkgs, home-manager, ... }: {
    darwinConfigurations."macbook" = darwin.lib.darwinSystem {
      system = "aarch64-darwin";
      modules = [
        ./darwin-configuration.nix
        home-manager.darwinModules.home-manager
        {
          home-manager.useGlobalPkgs = true;
          home-manager.useUserPackages = true;
          home-manager.users.alice = import ./home.nix;
        }
      ];
    };
  };
}

# Rebuild: darwin-rebuild switch --flake .#macbook

darwin-configuration.nix

{ pkgs, ... }: {

  # System packages
  environment.systemPackages = with pkgs; [
    vim git wget curl
  ];

  # Homebrew integration (for GUI apps / casks)
  homebrew = {
    enable = true;
    onActivation.cleanup = "zap";   # remove unlisted
    casks = [
      "firefox" "1password" "raycast"
      "wezterm" "visual-studio-code"
    ];
    brews = [ "mas" ];
    masApps = {
      "Xcode" = 497799835;
    };
  };

  # macOS system defaults
  system.defaults = {
    dock.autohide = true;
    dock.mru-spaces = false;
    finder.AppleShowAllExtensions = true;
    NSGlobalDomain.AppleShowAllExtensions = true;
    NSGlobalDomain.InitialKeyRepeat = 15;
    NSGlobalDomain.KeyRepeat = 2;
  };

  # Services
  services.nix-daemon.enable = true;

  # Enable Touch ID for sudo
  security.pam.services.sudo_local.touchIdAuth = true;

  system.stateVersion = 5;
}
12

Patterns & Pitfalls

Hard-won wisdom from the Nix community. Avoid the common traps and embrace the patterns that make Nix powerful.

Common Patterns

Pin nixpkgs for CI reproducibility

# flake.lock pins everything
# CI checks out repo, runs:
nix build  # uses exact same pkgs
nix flake check

Stable + Unstable packages

inputs.stable.url =
  "github:NixOS/nixpkgs/nixos-24.11";
inputs.unstable.url =
  "github:NixOS/nixpkgs/nixpkgs-unstable";

# Use unstable for specific pkgs
environment.systemPackages = [
  pkgs.git              # stable
  unstable.neovim       # bleeding edge
];

Modular config layout

modules = [
  ./hardware-configuration.nix
  ./modules/base.nix
  ./modules/networking.nix
  ./modules/desktop.nix
  ./modules/development.nix
  ./modules/gaming.nix
];

Share config across hosts

nixosConfigurations = {
  desktop = mkSystem [
    ./hosts/desktop.nix
    ./common/base.nix
  ];
  server = mkSystem [
    ./hosts/server.nix
    ./common/base.nix
  ];
};

Common Pitfalls

Pitfall Symptom Fix
Store bloat /nix/store uses 50+ GB Run nix-collect-garbage --delete-older-than 14d regularly; set nix.gc.automatic = true
Unfree package errors "Package is not free" build failure nixpkgs.config.allowUnfree = true; or per-package config.allowUnfreePredicate
Binary not found after install command not found Open a new shell or source ~/.nix-profile/etc/profile.d/nix.sh; check $PATH
Hash mismatch "hash mismatch in fixed-output derivation" Update the hash — use lib.fakeHash then copy the expected hash from the error
Infinite recursion "infinite recursion encountered" Use final/prev correctly in overlays; avoid self-referencing without rec
Slow evaluation Minutes to evaluate flake Use nix-direnv to cache; avoid import <nixpkgs> in flakes; reduce overlay complexity
Missing shared libraries "libxyz.so not found" at runtime Use autoPatchelfHook, or wrap with makeWrapper + LD_LIBRARY_PATH
FHS-expecting binaries Pre-compiled binary can't find /lib/ld-linux Use pkgs.buildFHSEnv or steam-run for stubborn binaries

Automatic Garbage Collection (NixOS)

# In configuration.nix / flake module
nix.gc = {
  automatic = true;
  dates = "weekly";
  options = "--delete-older-than 30d";
};

# Auto-optimize the store
nix.settings.auto-optimise-store = true;

Useful Tools & Resources

Tool Purpose
alejandra / nixfmt Nix code formatters (use with nix fmt)
nil / nixd Nix LSP servers for editor integration
nix-tree Interactive dependency tree viewer
nix-du Disk usage analyzer for the Nix store
nix-diff Diff two derivations to see what changed
nix-init Generate Nix packages from URLs
flake-checker Lint flake inputs for best practices
search.nixos.org Web search for packages and NixOS options
nix.dev Official Nix documentation and tutorials
wiki.nixos.org Community wiki with configuration examples