NixOS Packages Explained: 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.
- The Three Ways to Install a Package on NixOS
- nix-env — The Legacy Imperative Way
- environment.systemPackages — The Declarative Way
- nix profile install — The Modern Replacement for nix-env
- nix shell vs nix develop — Ephemeral Environments
- Finding Packages: nix search
- Overlays and Overrides — Customizing a Package
- Channels vs Flake Inputs — Pinning nixpkgs
- Understanding the Nix Store
- Generations, Rollbacks, and Cleaning the Store
- System Packages vs Home Manager — Which One?
- Troubleshooting
- Why doesn't my nix-env package show up after nixos-rebuild?
- Should I use flakes or channels for a new setup?
- How do I get a newer version of a package than what's in my nixpkgs pin?
- What's the difference between nix shell and nix develop?
- Why are my installed binaries in a folder with a random hash name?
- Further Reading
The Three Ways to Install a Package on NixOS
| Method | Scope | Style | Status in 2026 |
|---|---|---|---|
nix-env -i | Current user only | Imperative | Legacy — avoid for new setups |
environment.systemPackages | Whole system, all users | Declarative | Standard for system-wide tools |
nix profile install | Current user only | Declarative-ish, flake-based | Modern 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 firefoxIt 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 switchThis 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...#helloIt 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 jqFor 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 developUse 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 pythonThe 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.1That 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 --rollbackIf 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 bootFor 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 204Old 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 -dTo 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.
