You don't need Nix installed to understand it. You just need the right words.

Walk through any modern DevOps blog, GitHub repo, or dev conference talk and you'll trip over the same set of terms: Nix, NixOS, Nixpkgs, flakes, nix-shell, nix develop, overlays, Home Manager. People toss these around like they're obvious. For anyone who hasn't touched the ecosystem, they blur together into one opaque wall of jargon.

They're not the same thing. And once you can tell them apart, the whole ecosystem stops being mysterious — because each name refers to a distinct piece with a distinct job.

This guide exists to build that map. No installation, no commands to memorize, no pressure to switch your workflow. Just the vocabulary, explained, so the next time you read a config file or a blog post, the terms actually mean something.


Nix Package Manager

Nix (the underlying tool) is a package manager. But it's a package manager with an opinion: it treats software installation like pure functional programming.

Most package managers you know — apt, brew, dnf — mutate a system. They drop files into shared directories like /usr/bin, overwrite versions, and leave the system in a state that's the accumulation of everything you've ever installed. That's why uninstalling is messy, why two projects needing different versions of the same library clash, and why an upgrade can silently break something unrelated.

Nix refuses to do any of that. Its core rule:

The same input always produces the same output, and nothing mutates existing state.

Here's how it shows up in practice:

The Nix store. Every package lives in /nix/store under a path that includes a hash of everything that went into building it — something like /nix/store/g1a8d9...-hello-2.12.1. The hash means the path is deterministic: build the same package the same way and you get the exact same path. Install two versions of the same program and they live side by side, because each has a different hash. Nothing overwrites anything.

Derivations. Before Nix builds anything, it writes a derivation — a build recipe that lists every input, every dependency, and the exact build steps. A derivation is the "pure function" version of a package. Give it the same inputs and it produces the same result, every time, on any machine.

nix-env — the legacy imperative installer. Before declarative configs became the norm, this was how you'd install something with Nix day-to-day:

nix-env -iA nixpkgs.hello

This drops a package into your personal profile, imperatively — much closer to what apt install or brew install feels like. It still works, and you'll see it in older tutorials. But it quietly reintroduces the exact problem Nix is supposed to solve: your profile becomes "whatever you happened to run, in whatever order you ran it," which isn't reproducible or trackable the way a declarative configuration.nix or flake is. Think of nix-env as the on-ramp — useful for a quick one-off install or trying Nix for the first time — but the ecosystem has moved toward describing what you want in a file rather than running install commands one at a time.

Generations and profiles. Every time you install, upgrade, or remove something — whether through nix-env, nixos-rebuild switch, or a flake-based home-manager switch — Nix doesn't overwrite your existing setup. It builds a new generation: a complete, numbered snapshot of what your profile or system looked like at that point, linked together as a chain. Your profile (/nix/var/nix/profiles/...) is just a symlink pointing at whichever generation is currently active. This is why rollbacks work at all — the old generation never went away, it's just not the one currently pointed to.

Rollbacks. Because generations are immutable and never overwritten, switching back is just re-pointing the profile symlink at an older generation:

nix-env --rollback

or, on NixOS:

nixos-rebuild switch --rollback

Breaking upgrade? You're one command away from exactly what you had before. It's built-in version control for your software.

Garbage collection. Generations piling up in /nix/store isn't free — every version of everything you've ever built or installed sits on disk until you clean it up. That's what garbage collection is for:

nix-collect-garbage -d

This deletes old generations and anything in the store no longer referenced by a live GC root (a generation, a result symlink from a build, etc.). It's the maintenance half of the immutability story: Nix will happily keep every version forever unless you tell it otherwise, so knowing this command exists is the difference between "reproducible" and "my disk is full."

That's the mental model: Nix doesn't install software so much as compute it from a recipe, cache the result in the store, and point you at it. Nothing ever mutates what's already there — it just accumulates, until you decide to clean it up.


Nixpkgs

If Nix is the engine, Nixpkgs is the fuel — and the roadmap.

Nixpkgs is the giant, community-maintained repository of package definitions for the Nix ecosystem. It holds 80,000+ packages, each one written as a Nix expression: a recipe the Nix package manager can build.

Crucially, Nixpkgs isn't a separate package manager. It's a collection of definitions. When you reference a package by name — hello, python, firefox — you're pulling its recipe out of Nixpkgs and handing it to Nix to build.

And here's the elegant part: those recipes are written in the same Nix language used for everything else in the ecosystem. The language that builds a dependency tree for one project is the same language that describes an entire operating system. One language, one model, everywhere.

Overlays. Sometimes the version of a package in Nixpkgs isn't the one you want — maybe you need a newer version than what's currently packaged, a patch applied, or a build flag flipped. Rather than forking all of Nixpkgs, you write an overlay: a function that takes the existing package set and returns a modified one, layering your changes on top without touching the original definitions.

final: prev: {
  myapp = prev.myapp.overrideAttrs (old: {
    version = "2.0.1";
  });
}

Overlays are how customization happens without forking — you're not editing Nixpkgs itself, you're describing a diff against it. You'll see them constantly in NixOS configs and flakes once you're modifying anything beyond stock packages.

A quick mention: NUR. If overlays are how you customize a package, the Nix User Repository (NUR) is a community index of package definitions maintained by individual users, outside the official Nixpkgs review process. It's a way to get access to packages that haven't made it into Nixpkgs proper yet (or never will) — with the obvious caveat that it hasn't been through the same vetting. Worth knowing the name exists; not something you need to reach for on day one.


NixOS

NixOS is a Linux distribution built on top of the Nix package manager. It's what you get when you take Nix's functional philosophy and apply it to the whole operating system — not just the installed packages, but the system configuration itself.

On a traditional distro, you configure your system by editing scattered config files: /etc/ssh/sshd_config here, a networking file there, a display manager config somewhere else. Changes aren't tracked. Retrace your steps months later and you can't.

NixOS replaces that with one declarative specification. Your whole system — kernel, packages, services, users, networking, even the desktop environment — is described in a single configuration.nix file (or, these days, as part of a flake). Everything your machine is, in text. Version-controlled. Reproducible.

Change your mind about a service? Edit the file, run nixos-rebuild switch, and the system rebuilds to match — atomically. It either works or it doesn't; there's no half-upgraded in-between state. And because the old configuration still exists in the store as a previous generation, nixos-rebuild switch --rollback gives you your previous system back in seconds.

Important point for the vocabulary map: Nix and NixOS are not the same thing. Nix the package manager runs perfectly well on any Linux distro or macOS — you can install it on Ubuntu next to apt and use it for your own projects. NixOS is what you run as the operating system itself. The ecosystem's two tiers — "use Nix as a tool" and "run a Nix-built OS" — are separate decisions you make separately.


Home Manager

Here's a gap the previous section leaves open: NixOS declaratively manages the system — packages, services, the kernel. But what about your dotfiles, your shell config, your personal .gitconfig, your window manager keybindings? That's where Home Manager comes in.

Home Manager is a separate tool that applies the same declarative philosophy to your user environment rather than the whole OS. You describe the packages you want in your personal profile, your shell aliases, your editor config, your terminal emulator settings — all in Nix expressions — and it manages them the same way NixOS manages the system: atomically, reproducibly, with generations and rollbacks.

home.packages = [ pkgs.ripgrep pkgs.fzf ];
programs.zsh.enable = true;
programs.git.userName = "Sandbox Guy";

The part that matters most for the vocabulary map: Home Manager doesn't require NixOS. You can run it standalone on Ubuntu, Debian, Fedora, macOS — anywhere Nix the package manager is installed — and get declarative dotfile management without switching your entire OS. On NixOS specifically, it can also be integrated directly into your system flake, so your whole machine — OS and user environment — rebuilds from one nixos-rebuild switch.

This is the tool that made my own move to a full NixOS daily driver click into place: the OS config handles the system, Home Manager handles "me" — and both roll back the same way if something breaks.


Nix Flakes

Here's a term you'll see constantly in modern Nix configs: flakes.

A flake is a self-contained Nix project. At its root are two files:

  • flake.nix — the definition. It declares inputs (the other flakes your project depends on, like nixpkgs) and outputs (what your project produces).
  • flake.lock — the lockfile. It pins every input to an exact revision, so the whole dependency graph is frozen.

What flakes replaced: channels. Before flakes, the standard way to pin which version of Nixpkgs you were building against was a channel — a named, remotely-updated pointer (nixos-unstable, nixos-24.05, etc.) that you'd subscribe to with nix-channel --add and periodically update with nix-channel --update. The problem: channels update in place on your machine, on your schedule, which means "the same config" could quietly resolve to different package versions depending on when you last ran the update — the opposite of reproducible. flake.lock fixes that by pinning an exact commit hash rather than a moving named pointer. Channels still exist and plenty of NixOS installs still use them, but flakes are the direction the ecosystem has moved for anything meant to be reproducible.

Why flakes exist, in short:

  1. Reproducibility was aspirational, not guaranteed. Before flakes, your builds depended on whatever cherry-picked version of Nixpkgs your machine had at the moment. Two developers building the same project could get subtly different results. flake.lock kills that: same inputs, same outputs, every time, on any machine.
  2. Discoverability was poor. There was no standard shape for "a Nix project." Flakes give everything a uniform structure — if you see a flake.nix, you know exactly what it is and how to use it.

The outputs schema. Part of that uniform structure is a standard vocabulary for what a flake can produce. The common output types you'll see:

  • packages — buildable packages, referenced like .#my-package (what nix build targets)
  • devShells — declared development environments (what nix develop targets)
  • apps — runnable programs, invoked with nix run
  • nixosConfigurations — full system configs, referenced by nixos-rebuild switch --flake .#hostname
  • homeConfigurations — Home Manager configs, following the same pattern

You don't need to memorize all of these, but recognizing them means a flake.nix stops looking like an arbitrary blob and starts looking like a small, predictable menu of "here's what this project can do."

Think of flakes as Nix's answer to package.json + package-lock.json (or Cargo.toml + Cargo.lock): a standard project format with pinned dependencies.

Technically, flakes are still marked experimental — the nix command needs --experimental-features nix-command flakes — but they've become the de facto standard across the community. Nearly every current guide, template, and config you'll find uses them. You can treat "flake" as "modern standard Nix project."

Flakes are also where the modern command vocabulary lives, because the modern nix commands (below) are designed to work with them.


nix-shell

With the pieces in place, the commands become intuitive.

nix-shell is the legacy way to enter an interactive development environment. You tell it which packages you want (or point it at a derivation), and it starts a shell where only those packages are available — cleanly separated from the rest of your system.

The classic example:

nix-shell -p python312 nodejs

Gives you a shell with those exact versions of Python and Node, nothing else leaking in from your environment. Exit the shell, and it's as if they were never there.

For years nix-shell was the go-to for reproducible dev environments and for testing tools without polluting your system. It still works, and you'll still see it in older (and plenty of current) documentation. But the ecosystem has been moving to flakes — and the modern, preferred replacement command is nix develop.


nix develop

nix develop is the modern evolution of nix-shell. Instead of a shell you scavenge packages into, it starts a shell built from a declared development environment — usually defined in a flake's devShells output.

Walk into a flakes-based project that defines a dev shell and run:

nix develop

and you're dropped into a shell with every tool, library, and environment variable the project's authors declared. No manual -p python node ... hoisting — the environment is defined once, in flake.nix, and anyone who runs nix develop gets the same setup.

It's "here are the exact tools this project needs," reproduced identically for every developer on every machine. That's the reproducibility promise applied to the thing developers feel most often — their local environment.

Pairing with direnv. One friction point: nix develop only activates the shell for as long as you're inside it — cd out, and you lose the environment; cd back in, and you have to remember to re-run it. direnv (an existing shell tool, not Nix-specific) solves this by auto-loading or unloading environment variables based on your current directory, driven by an .envrc file. Paired with the nix-direnv extension, the pattern becomes:

# .envrc
use flake

Now simply cd-ing into the project directory silently activates the flake's devShells environment, and cd-ing out silently tears it down — no manual nix develop needed at all. This combination is common enough in the wild that it's worth recognizing on sight, even if you don't adopt it yourself.


nix build

nix build is how you build a package or derivation from a flake's packages output.

nix build .#my-package

Nix fetches every input, sandboxes the build so it can't touch anything outside its declared dependencies, runs the derivation's build steps, and deposits the result where you can find it — typically a result symlink pointing into the store.

Because builds are hermetic and rooted in pinned inputs (flakes again), building the same thing on a different machine yields the same store path. And if a binary of that exact derivation already exists in a shared cache called a binary cache or substituter, Nix just downloads it instead of recompiling — you don't have to build everything yourself.


Quick Reference

Command / term What it is When you'd use it
Nix Package manager; builds and stores software Installing packages, building, managing a store
nix-env Legacy imperative package installer Quick one-off installs, older tutorials
Generations / profiles Numbered snapshots + the pointer to the active one Understanding how rollbacks actually work
Garbage collection Deletes unreferenced store paths / old generations Reclaiming disk space
Nixpkgs Repo of 80k+ package definitions Referencing packages by name in configs
Overlays Function-based patches on top of Nixpkgs Customizing or overriding a package
NUR Community-maintained, unreviewed package index Finding packages not (yet) in Nixpkgs
NixOS Linux distro built on Nix Declarative, reproducible whole-system config
Home Manager Declarative user/dotfile config Managing your personal environment, with or without NixOS
Flake Self-contained flake.nix + flake.lock project Standard format for any modern Nix project
Channels Pre-flake, mutable Nixpkgs version pointer What flakes replaced; still seen on older setups
nix-shell Legacy dev shell command Quick, ad-hoc tool environments
nix develop Modern dev-shell command from a flake Reproducible, project-declared dev environments
direnv + nix-direnv Auto-loads a flake's dev shell per directory Skipping manual nix develop calls
nix build Build a derivation from a flake Package your software, test a build

Putting It All Together

Here's the whole map, extended:

  • Nix is the package manager — the engine that builds and stores software, tracked through generations and cleaned up with garbage collection.
  • Nixpkgs is the collection of package recipes it draws from, extendable with overlays and supplemented by community sources like NUR.
  • NixOS is an entire operating system built on top of that engine, reconstructed from a declarative config.
  • Home Manager takes the same declarative approach and applies it to you — your dotfiles and user environment — on NixOS or anywhere else.
  • Flakes are the standard project format (with pinned dependencies) that replaced the older, mutable channels, tying the modern commands together via a predictable outputs schemanix-shell (legacy) / nix develop (modern, often paired with direnv) for dev environments, nix build for building software.

You now have the vocabulary. That's the hard part, honestly — the terms are what make everything else legible.

If the map piqued your interest and you want to go further: zero-to-nix.com is the friendliest on-ramp, the official Nix manual and nix.dev get into the how, NixOS Pills (a free long-form tutorial) teaches the internals hands-on, and the Home Manager manual covers the user-environment side once you're ready to look at it.

No swearing-in yet required. The vocabulary alone gets you most of the way there.