# Getting Started with Nix

[Nix](https://nixos.org/) 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.

<iframe width="100%" height="315" src="https://www.youtube.com/embed/tKqBLvjka8o" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>


## 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 `sudo` access
* **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?

![Screenshot of Nix website homepage](https://imagedelivery.net/xZXo0QFi-1_4Zimer-T0XQ/e1a19361-11e4-4ea9-b054-984bceea2700/lg2x =2978x1550)

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:

```command
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:

```command
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:

```command
nix --version
```

```text
[output]
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:

```command
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:

```text
[output]
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:

```command
rg --version
```

```text
[output]
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:

```command
nix-env -iA nixpkgs.git nixpkgs.nodejs nixpkgs.python312
```

Want to see what you've installed?

```command
nix-env -q
```

```text
[output]
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](https://search.nixos.org/packages). From the terminal:

```command
nix-env -qaP | grep postgresql
```

```text
[output]
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:

```command
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:

```command
nix-channel --update
```

```text
[output]
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:

```command
nix-env -u
```

```text
[output]
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:

```command
nix-env -u nodejs
```

Remove packages you no longer need:

```command
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:

```command
nix-env --list-generations
```

```text
[output]
   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:

```command
nix-env --rollback
```

```text
[output]
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:

```command
nix-env --switch-generation 2
```

Over time, the Nix store accumulates packages from old generations. Reclaim disk space by removing packages nothing references anymore:

```command
nix-collect-garbage
```

This removes unreferenced packages but preserves all generations. To delete old generations and their packages:

```command
nix-collect-garbage -d
```

```text
[output]
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:

```command
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:

```command
nix-store -q --references $(which ripgrep)
```

```text
[output]
/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:

```command
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:

```command
nix-shell -p python312 python312Packages.requests
```

```text
[output]
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:

```command
python --version
```

```text
[output]
Python 3.12.8
```

```command
python -c "import requests; print(requests.__version__)"
```

```text
[output]
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?

```command
nix-shell -p nodejs-18_x
```

```command
node --version
```

```text
[output]
v18.20.5
```

For projects requiring specific dependencies, create a `shell.nix` file in your project directory. This defines the environment declaratively:

```nix
[label shell.nix]
{ 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:

```command
nix-shell
```

```text
[output]
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`:

```command
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:

```command
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:

```nix
[label shell-dev.nix]
{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = [
    pkgs.nodejs
    pkgs.nodePackages.typescript
    pkgs.nodePackages.eslint
  ];
}
```

```nix
[label shell-prod.nix]
{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = [
    pkgs.nodejs
  ];
}
```

Enter different environments with:

```command
nix-shell shell-dev.nix
```

```command
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](https://nixos.org/manual/nix/stable/).

Thanks for reading!