Getting Started with Nix
Nix is a powerful open-source package manager that works across Linux, macOS, and Windows via WSL. It provides declarative package management with reproducible builds and atomic upgrades that keep your development environment consistent across machines.
In this guide, you'll learn how Nix approaches package management differently, how to use it effectively, and how it solves problems that traditional package managers can't handle.
Prerequisites
Before diving into Nix, make sure your system meets these requirements:
- macOS: macOS 10.15 (Catalina) or later with at least 2GB of free disk space
- Linux: Any modern distribution with kernel 2.6 or later and
sudoaccess - Windows (WSL): Windows 10/11 with WSL2 and a Linux distribution installed (Ubuntu works well)
Nix runs in user space and doesn't require special permissions beyond what you'd give any package manager during installation.
What is Nix and why use it?
Before exploring Nix's technical capabilities, understanding its philosophy reveals why it represents such a fundamental shift in package management—especially for macOS users.
The declarative approach
Nix distinguishes itself from traditional package managers through its purely functional and declarative design.
Traditional package managers like Homebrew use an imperative approach. You run sequential commands that modify your system's state: brew install wget, then later brew uninstall wget. Each command tells the system how to change. If you want to track what you've installed, you need to remember or document every command you've run.
Nix takes a declarative approach instead. You write a configuration file describing the final state you want: "my system should have wget, neovim, and zellij installed." Run a single command, and Nix calculates the necessary steps to match that declaration. Remove wget from the file and rebuild—Nix automatically uninstalls it without explicit removal commands.
This distinction matters because your entire system configuration becomes a versioned text file. Store it in a Git repository, and you gain version control over your machine's setup. Getting a new Mac? Clone your repository, run one command, and Nix replicates your entire environment—every package, every setting—identically.
Reliability through immutability
Nix installs packages into unique directories within the Nix store at /nix/store. Each package is immutable—it never changes after installation.
When you update a package, Nix builds the new version in a separate directory, then atomically switches a symlink to point to it. Updates can never produce broken, half-configured states. If an upgrade fails, your system remains untouched because nothing was modified in place.
This architecture enables you to have multiple versions of the same package installed simultaneously without conflicts. You can also roll back to any previous configuration instantly—not just the last one, but any historical state you've created.
From NixOS to nix-darwin
Nix originated in 2003 from Dutch computer scientist Eelco Dolstra's PhD research. His work produced the Nix package manager, the Nix programming language (a functional language for writing configurations), and NixOS—a Linux distribution built entirely on Nix principles. On NixOS, everything from the kernel to user applications is managed declaratively.
For macOS users, the nix-darwin project brings this ecosystem to your Mac. It extends Nix's declarative configuration model to manage not just packages, but macOS system settings, services, and environment variables. By hooking into Darwin—the Unix-like foundation of macOS—nix-darwin provides unified control over your entire machine configuration.
Throughout this guide, you'll see how Nix's unique approach translates into practical benefits for your daily development work.
Installing Nix
Getting Nix running on your machine requires a single installation command that sets up everything automatically.
The official installer configures Nix for your user account without affecting system-wide software. This means you can experiment safely—if you decide Nix isn't for you, uninstalling leaves your system unchanged.
Open your terminal and run:
sh <(curl -L https://nixos.org/nix/install)
The installer explains each step as it progresses. It creates the /nix directory where all packages live and configures your shell to recognize Nix commands. You'll need to enter your password when prompted.
Installation typically takes 5-10 minutes depending on your connection speed. The script downloads the Nix binaries and sets up the package database.
After installation completes, you need to reload your shell configuration. The installer shows the exact command, but it's usually:
source ~/.nix-profile/etc/profile.d/nix.sh
For permanent setup, the installer adds this to your shell's configuration file (.bashrc, .zshrc, or similar) so Nix works in future terminal sessions automatically.
Verify the installation by checking the version:
nix --version
nix (Nix) 2.33.0
Perfect! Nix is ready to manage packages on your system.
Getting started with Nix
Let's explore how Nix handles package installation and management. The commands differ from traditional package managers, but the underlying concepts become clear quickly.
Installing software with Nix uses profiles—think of these as named collections of packages. Install a package into your user profile like this:
nix-env -iA nixpkgs.ripgrep
The -iA flag means "install by attribute"—you're referencing the package by its exact name in the Nix package repository. The installation output shows what's happening:
installing 'ripgrep-15.1.0'
these 8 paths will be fetched (10.9 MiB download, 49.4 MiB unpacked):
/nix/store/xm08aqdd7pxcdhm0ak6aqb1v7hw5q6ri-gcc-14.3.0-lib
/nix/store/shs2lyjm5yv7fmg4pwyvrlv9zr4macmv-gcc-14.3.0-libgcc
/nix/store/xx7cm72qy2c0643cm1ipngd87aqwkcdp-glibc-2.40-66
/nix/store/hxcmad417fd8ql9ylx96xpak7da06yiv-libidn2-2.3.8
/nix/store/3rkccxj7vi0p2a0d48c4a4z2vv2cni88-libunistring-1.4.1
Each package gets installed to a unique path in /nix/store identified by a cryptographic hash. Nix then creates symlinks in your profile so you can run the commands.
Test that it works:
rg --version
ripgrep 15.1.0
features:+pcre2
simd(compile):+SSE2,-SSSE3,-AVX2
simd(runtime):+SSE2,+SSSE3,+AVX2
PCRE2 10.45 is available (JIT is available)
You can install multiple packages at once:
nix-env -iA nixpkgs.git nixpkgs.nodejs nixpkgs.python312
Want to see what you've installed?
nix-env -q
git-2.47.1
nodejs-22.12.0
python3-3.12.8
ripgrep-14.1.1
Searching for available packages works through the command line or the Nix package search website. From the terminal:
nix-env -qaP | grep postgresql
nixpkgs.postgresql_14 postgresql-14.15
nixpkgs.postgresql_15 postgresql-15.10
nixpkgs.postgresql_16 postgresql-16.6
nixpkgs.postgresql postgresql-17.2
The first column shows the attribute path (what you use with -iA), and the second shows the actual package name and version.
Get detailed information about a package before installing:
nix-env -qa --description ripgrep
This shows the version, description, and where it's available in the package repository.
Nix maintains a clear separation between packages in the store and packages in your profile. This distinction enables the rollback and garbage collection features you'll use later.
Managing packages with Nix
Nix's approach to package maintenance differs significantly from traditional package managers. Instead of modifying packages in place, it creates new generations of your profile.
Updating your package list fetches the latest package definitions:
nix-channel --update
unpacking channels...
This updates the nixpkgs channel—Nix's equivalent of a package repository. Think of channels as streams of package versions that you subscribe to.
Upgrade all installed packages to their latest versions:
nix-env -u
upgrading 'git-2.47.1' to 'git-2.48.0'
upgrading 'nodejs-22.12.0' to 'nodejs-22.13.1'
Nix downloads new versions without touching the old ones. Both versions exist in /nix/store simultaneously.
Upgrade a specific package only:
nix-env -u nodejs
Remove packages you no longer need:
nix-env -e nodejs
This removes the package from your profile but leaves it in the store. The old package versions remain available if you need to roll back.
Here's where Nix's generation system becomes powerful. Every time you install, upgrade, or remove packages, Nix creates a new generation of your profile. List all generations:
nix-env --list-generations
1 2024-11-15 10:30:22
2 2024-11-20 14:15:33
3 2024-12-01 09:45:12
4 2024-12-10 16:22:45 (current)
If an upgrade breaks something, roll back to the previous generation instantly:
nix-env --rollback
switching from generation 4 to 3
Your environment reverts to exactly how it was before the upgrade. No reinstallation, no configuration changes—just an atomic switch to the previous state.
You can also switch to any specific generation:
nix-env --switch-generation 2
Over time, the Nix store accumulates packages from old generations. Reclaim disk space by removing packages nothing references anymore:
nix-collect-garbage
This removes unreferenced packages but preserves all generations. To delete old generations and their packages:
nix-collect-garbage -d
removing old generations of profile /nix/var/nix/profiles/per-user/stanley/profile
finding garbage collector roots...
deleting garbage...
deleting '/nix/store/abc123...-nodejs-22.12.0'
deleting '/nix/store/def456...-git-2.47.1'
freed 543.2 MiB
Check which packages depend on a specific package:
nix-store -q --referrers /nix/store/xyz789...-openssl-3.0.15
This shows everything that needs that particular package to function.
See what a package depends on:
nix-store -q --references $(which ripgrep)
/nix/store/abc123...-glibc-2.39-52
/nix/store/def456...-gcc-13.2.0-lib
/nix/store/ghi789...-ripgrep-14.1.1
If something seems broken, Nix can verify package integrity:
nix-store --verify --check-contents
This confirms that packages in the store match their expected cryptographic hashes.
The generation system and garbage collection work together to give you both safety and space efficiency. You can experiment freely knowing you can always roll back, then clean up old versions when you're confident in your current setup.
Development environments with Nix
Beyond managing system packages, Nix excels at creating isolated development environments. This feature addresses one of the most common developer frustrations—dependency conflicts between projects.
A Nix shell creates a temporary environment with specific packages available. You can enter a shell with any tool without installing it permanently:
nix-shell -p python312 python312Packages.requests
these 15 paths will be fetched (45.3 MB download)...
downloading...
[nix-shell:~]$
Inside this shell, Python 3.12 and the requests library are available:
python --version
Python 3.12.8
python -c "import requests; print(requests.__version__)"
2.31.0
Exit the shell with exit or Ctrl+D. The packages disappear from your environment immediately—they were only available inside that shell session.
This temporary environment approach works perfectly for quick experiments or running unfamiliar tools. Want to try a different Node.js version without affecting your installed version?
nix-shell -p nodejs-18_x
node --version
v18.20.5
For projects requiring specific dependencies, create a shell.nix file in your project directory. This defines the environment declaratively:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.python312
pkgs.python312Packages.django
pkgs.python312Packages.psycopg2
pkgs.postgresql_16
];
shellHook = ''
echo "Django development environment loaded"
export DATABASE_URL="postgresql://localhost/mydb"
'';
}
Navigate to your project directory and run:
nix-shell
Django development environment loaded
[nix-shell:~/myproject]$
Now you have Python 3.12, Django, PostgreSQL client libraries, and PostgreSQL 16 available. The shellHook runs automatically when entering the shell, setting up environment variables or starting services.
Every developer on your team who runs nix-shell in this project gets the identical environment. No more "works on my machine" problems caused by different installed versions.
For even more reproducibility, use nix-shell --pure. This strips away all your existing environment variables and packages, giving you only what's declared in shell.nix:
nix-shell --pure
This mode reveals hidden dependencies—if your project works in a pure shell, it will work on any machine with Nix.
You can also run single commands in a Nix environment without entering the shell interactively:
nix-shell -p nodejs --run "node script.js"
This runs the command with Node.js available, then exits. Perfect for scripts or CI/CD pipelines.
For more complex projects, create multiple shell environments in separate files:
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.nodejs
pkgs.nodePackages.typescript
pkgs.nodePackages.eslint
];
}
{ pkgs ? import <nixpkgs> {} }:
pkgs.mkShell {
buildInputs = [
pkgs.nodejs
];
}
Enter different environments with:
nix-shell shell-dev.nix
nix-shell shell-prod.nix
The development shell includes linting and TypeScript compilation tools, while the production shell has only the Node.js runtime.
Teams can commit shell.nix files to version control. This documents dependencies explicitly and ensures consistent environments across local development, testing, and CI systems.
Nix shells transform dependency management from implicit (hoping everyone has the right versions installed) to explicit (declaring exactly what your project needs).
Final thoughts
This article covered Nix's fundamental features to help you manage software more reliably. With Nix, you get reproducible package installations, atomic rollbacks, and isolated development environments that solve real problems traditional package managers can't address.
To explore advanced features like building custom packages, writing more complex expressions, and using NixOS, visit the official Nix documentation.
Thanks for reading!