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 (likefetchurl) 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 -ddeletes ALL old generations, including the one you would roll back to if the current generation is broken. On NixOS, alwaysnix-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