NixOS Packages Explained: nix-env, nix profile, and the Nix Store

NixOS package management diagram: nix-env, nix profile and the Nix store

✅ Verified against Nix 2.24 manual and nix.dev — Last updated: September 2026

If you came from Ubuntu or Arch, sudo apt install and pacman -S taught you one mental model: one command, one package, done. NixOS breaks that model on purpose. There are three different ways to install a package, they don't share the same scope, and using the wrong one for the wrong job is the single most common source of "why isn't my package showing up" confusion. This guide covers what each one actually does, when to use it, and the store mechanics underneath all of them. If you haven't installed NixOS yet, start with the flakes-first installation guide — this post assumes you already have a working system.

Contents
  1. The Three Ways to Install a Package on NixOS
  2. nix-env — The Legacy Imperative Way
  3. environment.systemPackages — The Declarative Way
  4. nix profile install — The Modern Replacement for nix-env
  5. nix shell vs nix develop — Ephemeral Environments
  6. Finding Packages: nix search
  7. Overlays and Overrides — Customizing a Package
  8. Channels vs Flake Inputs — Pinning nixpkgs
  9. Understanding the Nix Store
  10. Generations, Rollbacks, and Cleaning the Store
  11. System Packages vs Home Manager — Which One?
  12. Troubleshooting
    1. Why doesn't my nix-env package show up after nixos-rebuild?
    2. Should I use flakes or channels for a new setup?
    3. How do I get a newer version of a package than what's in my nixpkgs pin?
    4. What's the difference between nix shell and nix develop?
    5. Why are my installed binaries in a folder with a random hash name?
    6. Further Reading

The Three Ways to Install a Package on NixOS

MethodScopeStyleStatus in 2026
nix-env -iCurrent user onlyImperativeLegacy — avoid for new setups
environment.systemPackagesWhole system, all usersDeclarativeStandard for system-wide tools
nix profile installCurrent user onlyDeclarative-ish, flake-basedModern replacement for nix-env, still marked experimental by Nix itself

Each one writes to a different place, which is why mixing them causes the classic "I installed it but it's not there" bug covered below.

nix-env — The Legacy Imperative Way

This is the closest thing to apt install that Nix has:

nix-env -i firefox
# or
nix-env --install firefox

It modifies your user profile directly ($HOME/.nix-profile) with no declarative record anywhere in your config files. That's exactly the problem: reinstall NixOS, or move to another machine, and every nix-env package is gone with no list to replay from. It still works, and you'll see it in old tutorials, but for a new NixOS setup in 2026, use environment.systemPackages or nix profile install instead.

environment.systemPackages — The Declarative Way

This is how you install a package system-wide, for every user, and have it survive a full reinstall because it's written down:

# /etc/nixos/configuration.nix
environment.systemPackages = with pkgs; [
  firefox
  htop
  ripgrep
];

Nothing installs until you apply it:

sudo nixos-rebuild switch

This is the method to reach for by default. It's slower than nix-env for a one-off "let me just try this tool" install (you have to edit a file and rebuild), but it's the only method that makes your whole system reproducible from one file.

nix profile install — The Modern Replacement for nix-env

If you specifically want a per-user, imperative install without touching configuration.nix — but without the "no record of what I installed" problem of nix-env — use the flake-based profile command:

nix profile install nixpkgs#hello

# pin to a specific nixpkgs branch
nix profile install nixpkgs/release-25.05#hello

# pin to an exact commit, for full reproducibility
nix profile install nixpkgs/d73407e8...#hello

It tracks installed packages in a manifest, which nix-env never did properly. One honest caveat: the Nix manual itself still labels nix profile as experimental, with an interface that "is subject to change." It's the direction Nix is heading, and it's what most current tutorials use — but don't be surprised if the exact flags shift in a future release.

nix shell vs nix develop — Ephemeral Environments

Neither of these "installs" anything permanently — they drop you into a temporary environment with packages available only for that shell session.

# quick, throwaway: puts binaries on PATH, nothing else
nix shell nixpkgs#nodejs nixpkgs#jq

# legacy pre-flakes equivalent
nix-shell -p nodejs jq

For actual project development, nix develop is the flake-native tool — it builds an environment close to what Nix would use to build the package itself, based on a devShells output in your project's flake.nix:

# flake.nix
{
  outputs = { self, nixpkgs }: let
    system = "x86_64-linux";
    pkgs = nixpkgs.legacyPackages.${system};
  in {
    devShells.${system}.default = pkgs.mkShell {
      buildInputs = [ pkgs.nodejs pkgs.jq ];
    };
  };
}
nix develop

Use nix shell for "I need jq for two minutes." Use nix develop for an actual project's dev environment that other contributors can reproduce exactly.

Finding Packages: nix search

The current command-line search uses a regex against package names, versions and descriptions:

nix search nixpkgs firefox

# OR pattern
nix search nixpkgs 'firefox|chromium'

# exclude matches
nix search nixpkgs blender --exclude python

The web equivalent is search.nixos.org/packages, which also lets you search NixOS options (configuration.nix settings) separately from packages — useful once you're past basic installs and configuring services declaratively.

Overlays and Overrides — Customizing a Package

Sometimes the version of a package in nixpkgs isn't quite what you need. Nix gives you two tools, at two different scopes:

  • override — changes the arguments passed into one package's build, e.g. swapping a dependency version for that package only.
  • Overlay — a function that can modify many interdependent packages across the entire nixpkgs set at once, applied globally.
# overlay.nix — modifies boost's python dependency and adds a custom package
final: prev: {
  boost = prev.boost.override { python = final.python3; };
  rr = prev.callPackage ./pkgs/rr { stdenv = final.stdenv_32bit; };
}

Reach for override when you're tweaking one package. Reach for an overlay when the change needs to be visible to every other package that depends on it too.

Channels vs Flake Inputs — Pinning nixpkgs

Every package you install comes from some version of the nixpkgs repository. Historically that was controlled by channels (nix-channel); with flakes, it's controlled by the flake.lock file generated automatically the first time you build something.

To be precise about where NixOS's official docs stand on this in 2026: nix.dev does not present flakes as unconditionally settled. It still describes flakes as "an experimental extension with outstanding problems," while acknowledging most current tutorials and tooling assume them. Read that as: flakes are the pragmatic default worth learning, not unanimous official doctrine — the same nuance covered in the installation guide.

In practice, flake.lock is what makes a config reproducible: it pins the exact commit of nixpkgs (and any other flake inputs) your system was built against, so the same flake.nix produces the same system a year later.

Understanding the Nix Store

Every package NixOS installs lands in /nix/store, under a path like:

/nix/store/q06x3jll2yfzckz2bzqak089p43ixkkq-firefox-33.1

That hash prefix isn't cosmetic — it's derived from the package's exact inputs (source, dependencies, build instructions). Change any input and you get a different hash and a different store path. This is what lets multiple versions of the same package coexist without conflict, and what makes a NixOS config reproducible: the hash is the guarantee that "firefox-33.1" means the exact same bytes on every machine that builds it.

Generations, Rollbacks, and Cleaning the Store

This is the payoff for everything above: because every change creates new, immutable entries in the store instead of overwriting anything, every nixos-rebuild switch produces a new numbered generation — and you can go back.

# roll back the whole system to the previous generation
sudo nixos-rebuild switch --rollback

# roll back a user profile (nix-env / nix profile installs)
nix-env --rollback

If a broken update won't even boot cleanly, you don't need either command — NixOS adds every generation as a separate entry in the bootloader menu, so you can boot straight into the last working one. To make that booted generation the new default afterward:

/run/current-system/bin/switch-to-configuration boot

For finer control than a plain rollback — listing exact generations, or jumping to a specific one, not just "the previous one":

# list system generations (as root)
nix-env --list-generations --profile /nix/var/nix/profiles/system

# switch to a specific generation number
sudo nix-env --profile /nix/var/nix/profiles/system --switch-generation 204

Old generations aren't free — every one keeps its packages alive in /nix/store until you garbage-collect. That's the tradeoff for instant rollbacks: the store grows. Clean it up with:

# delete generations older than 30 days, keep recent ones for rollback
nix-collect-garbage --delete-older-than 30d

# delete ALL old generations — you lose rollback capability for anything removed
nix-collect-garbage -d

To avoid doing this manually forever, set nix.gc in configuration.nix to run garbage collection automatically on a schedule — search the nix.gc options for the exact schedule and retention settings.

System Packages vs Home Manager — Which One?

environment.systemPackages installs for every user on the machine and requires root (via nixos-rebuild). home-manager manages packages and dotfiles per user, declaratively, without touching system config at all:

# home.nix (home-manager)
home.packages = with pkgs; [ ripgrep fzf ];

Rule of thumb: put anything genuinely system-level (drivers, services, firewall tools) in systemPackages. Put your personal CLI tools, shell config and dotfiles in home-manager — especially useful if you share a machine or want your user environment to travel to a non-NixOS Linux box via home-manager's standalone mode.

Troubleshooting

Why doesn't my nix-env package show up after nixos-rebuild?

nix-env installs into $HOME/.nix-profile, a completely separate scope from environment.systemPackages. Running nixos-rebuild never touches your user profile, and installing something via configuration.nix never touches nix-env's profile either. Check which scope you actually used with nix-env -q (user profile) versus grepping your configuration.nix.

Should I use flakes or channels for a new setup?

Flakes, for reproducibility and because that's what most current guides (including this site's) assume — but know it's still officially labeled experimental by the Nix project itself, not a permanently settled interface.

How do I get a newer version of a package than what's in my nixpkgs pin?

Point that one input at nixpkgs-unstable instead of your stable channel/branch, or run nix flake update to bump your existing flake.lock to the latest commit on the branch you're already tracking.

What's the difference between nix shell and nix develop?

nix shell just puts binaries on your PATH for a quick one-off. nix develop builds a full development environment from a devShells flake output, meant for an actual project other people can reproduce.

Why are my installed binaries in a folder with a random hash name?

That's /nix/store/<hash>-packagename. The hash is derived from the package's exact build inputs, which is how Nix guarantees that path always points to that exact build — and how multiple versions of the same package can exist side by side without conflict.


Go up

This site uses cookies for analytics and advertising (Google AdSense). By continuing to browse, you accept our use of cookies. Learn more