Skip to content

Nix / NixOS - Street-Level Ops

What experienced Nix operators know that tutorials don't teach.

Quick Diagnosis Commands

# Show what's installed in the current profile
nix-env -q

# Search for a package in nixpkgs
nix search nixpkgs firefox
nix-env -qaP 'firefox'          # legacy, includes attribute path

# Show the nix store path for a derivation
nix-build '<nixpkgs>' -A python3 --no-out-link
nix eval nixpkgs#python3.outPath

# Show what a derivation depends on (runtime closure)
nix-store --query --references /nix/store/<hash>-python3-3.11.6
nix-store --query --tree /nix/store/<hash>-python3-3.11.6

# Compute closure size
nix path-info --recursive --size /nix/store/<hash>-... | awk '{sum+=$1} END {print sum/1024/1024 " MB"}'

# Show what uses a path (reverse deps)
nix-store --query --referrers /nix/store/<hash>-openssl-3.1.1

# List all generations of a profile
nix-env --list-generations
sudo nix-env --list-generations -p /nix/var/nix/profiles/system   # NixOS system profile

# Garbage collection
nix-collect-garbage              # collect dead store paths
nix-collect-garbage -d           # delete ALL old generations first, then collect
sudo nix-collect-garbage -d      # system-wide (NixOS) — safe to run
nix store gc                     # flakes-era equivalent

# Show disk usage of /nix/store
du -sh /nix/store
nix store info                   # store statistics (flakes)

# What does this flake expose?
nix flake show github:NixOS/nixpkgs
nix flake show .

# Update flake inputs
nix flake update
nix flake update nixpkgs          # update only nixpkgs input

# Check flake metadata / lock file
nix flake metadata .
cat flake.lock | jq '.nodes.nixpkgs.locked'

# Verify store integrity
nix-store --verify --check-contents --repair

# Show build log for a package
nix log /nix/store/<hash>-myapp

Common Scenarios

Scenario 1: Reproducible Dev Environment with nix develop

You want everyone on the team to use identical tooling without polluting their system.

# Enter a flake dev shell
nix develop                      # uses flake.nix in CWD
nix develop .#backend            # named devShell

# One-off shell with specific packages (no flake needed)
nix shell nixpkgs#nodejs_20 nixpkgs#yarn

# Legacy (non-flake) nix-shell
nix-shell -p nodejs yarn python3

# Pin nixpkgs revision in nix-shell (without flakes)
nix-shell -I nixpkgs=https://github.com/NixOS/nixpkgs/archive/22.11.tar.gz -p nodejs

Minimal flake.nix for a dev shell:

{
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";

  outputs = { self, nixpkgs }:
    let
      system = "x86_64-linux";
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      devShells.${system}.default = pkgs.mkShell {
        packages = with pkgs; [
          nodejs_20
          yarn
          python311
          postgresql_15
          redis
        ];
        shellHook = ''
          echo "Dev environment ready. Node: $(node --version)"
          export DATABASE_URL="postgresql://localhost/myapp_dev"
        '';
      };
    };
}

# Commit flake.nix and flake.lock together
git add flake.nix flake.lock
git commit -m "feat: add nix dev shell"

Scenario 2: Build Fails with "Impure" or Network Access Denied

Nix builds run in a sandbox with no network access. A fetchurl or buildPythonPackage fails because it tries to download at build time.

error: cannot open '/proc/sys/kernel/hostname': Permission denied
error: network access is not allowed in pure derivations

Diagnosis and fix:

# Wrong: fetching inside a derivation builder function
mkDerivation {
  buildPhase = ''
    curl https://example.com/data.tar.gz | tar xz   # FAILS in sandbox
  '';
}

# Right: fetch at evaluation time using a fixed-output derivation
src = fetchurl {
  url = "https://example.com/data-1.0.tar.gz";
  sha256 = "sha256-abc123...";  # must be correct or build fails
};

Under the hood: Nix's sandbox uses Linux namespaces to create a build environment with no network access and an empty /tmp. Fixed-output derivations (like fetchurl) are the only exception -- they are allowed network access because their output is verified by hash. If the hash doesn't match, the build fails deterministically regardless of what was downloaded.

# Get the correct hash for a URL
nix-prefetch-url https://example.com/data-1.0.tar.gz
nix store prefetch-file https://example.com/data-1.0.tar.gz   # flakes-era

# For git repos
nix-prefetch-git https://github.com/org/repo --rev abc123

Scenario 3: Garbage Collection Deletes a Build You Need

You ran nix-collect-garbage -d and lost a build artifact or an old system generation you wanted to roll back to.

Prevention — add GC roots:

# Keep a result symlink as a GC root
nix-build . -o ./result              # ./result symlink is a GC root
ls -la /nix/var/nix/gcroots/auto/   # auto GC roots from symlinks in home

# Add explicit GC root
nix-store --add-root /nix/gcroots/my-build --indirect -r /nix/store/<hash>

# See what GC roots exist
nix-store --gc --print-roots | grep -v /proc

NixOS system generations:

# List system generations before GC
sudo nix-env --list-generations -p /nix/var/nix/profiles/system

# Keep last N generations, delete older
sudo nix-env --delete-generations +5 -p /nix/var/nix/profiles/system

# Roll back to previous generation
sudo nixos-rebuild switch --rollback
sudo nix-env --rollback -p /nix/var/nix/profiles/system

Scenario 4: Override a Package in nixpkgs (Overlay Pattern)

You need a patched version of a package or a different build option.

# In flake.nix
outputs = { self, nixpkgs }: {
  nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
    modules = [
      ({ pkgs, ... }: {
        nixpkgs.overlays = [
          (final: prev: {
            myapp = prev.myapp.overrideAttrs (old: {
              patches = old.patches or [] ++ [ ./my-fix.patch ];
              version = "1.2.3-patched";
            });
          })
        ];
      })
    ];
  };
};
# Test an overlay without committing to a system rebuild
nix repl
nix-repl> pkgs = import <nixpkgs> { overlays = [ (f: p: { mypkg = p.mypkg.override { enableFoo = true; }; }) ]; }
nix-repl> pkgs.mypkg

Key Patterns

home-manager Basics

# Install home-manager (standalone, not NixOS module)
nix-channel --add https://github.com/nix-community/home-manager/archive/release-24.05.tar.gz home-manager
nix-channel --update
nix-shell '<home-manager>' -A install

# Activate a configuration
home-manager switch
home-manager switch --flake .#myuser@myhost

# Minimal ~/.config/home-manager/home.nix
{ pkgs, ... }: {
  home.username = "alice";
  home.homeDirectory = "/home/alice";
  home.stateVersion = "24.05";

  home.packages = with pkgs; [ ripgrep fd bat jq fzf htop ];

  programs.git = {
    enable = true;
    userName = "Alice";
    userEmail = "alice@example.com";
  };
}

nix-env vs Flakes (Operational Differences)

# nix-env (legacy, imperative, NOT reproducible)
nix-env -iA nixpkgs.ripgrep           # install to user profile
nix-env -e ripgrep                    # uninstall
nix-env --rollback                    # undo last change
# Problem: different machines may get different versions depending on channel date

# flakes (declarative, reproducible, pinned)
nix profile install nixpkgs#ripgrep   # flakes-era install
nix profile list
nix profile remove <number>
# flake.lock pins exact revision — same version everywhere, always

Gotcha: nix-collect-garbage -d deletes ALL old generations, including the one you would roll back to if the current generation is broken. On NixOS, always nix-env --delete-generations +3 (keep 3 most recent) instead of -d, so you retain a known-good system generation to boot into.

Nix Store Path Anatomy

/nix/store/ywimqmj9yavq47hd0q6wrcwhq3z8p7xz-python3-3.11.6
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^  ^^^^^^^^^^^^^^
            hash (sha256 of inputs)          name-version
# A path is valid as long as:
# 1. It exists in /nix/store
# 2. At least one GC root points to it (directly or transitively)

# Check if a path is live
nix-store --query --roots /nix/store/<hash>-myapp

# Find store paths by name
ls /nix/store | grep python3-3.11

NixOS Rebuild Workflow

# Test config without activating (check for errors only)
sudo nixos-rebuild dry-build

# Build and activate (apply to running system)
sudo nixos-rebuild switch

# Build, activate, and set as boot default
sudo nixos-rebuild boot

# Test new config but revert on reboot (safe testing)
sudo nixos-rebuild test

# Build for a remote host
nixos-rebuild switch --flake .#remote-host --target-host user@192.168.1.10 --use-remote-sudo

Debugging Build Failures

# Drop into a shell with the build environment populated
nix develop .#mypackage
nix-shell '<nixpkgs>' -A mypackage    # legacy

# Build with verbose output
nix build .#mypackage -L             # show build log
nix build .#mypackage --keep-failed  # keep /tmp/nix-build-* on failure

# Run only specific phases
nix-shell '<nixpkgs>' -A mypackage --run 'unpackPhase && configurePhase'

# Check what phase failed
# Build logs are in /nix/var/log/nix/ or via:
nix log /nix/store/<hash>-mypackage

Common nixpkgs Attributes

# Python packages
nix shell nixpkgs#python311Packages.requests

# Per-language ecosystems
nix shell nixpkgs#nodePackages.prettier
nix shell nixpkgs#rubyPackages.rails

# Pinning nixpkgs to a specific commit
nix shell github:NixOS/nixpkgs/abc123def#ripgrep