Stop using nix-env.
For the sake of you and others.
nix-env was built as a tool for Nix
as a way to manage packages in a traditional (imperative) fashion.
It tries to bridge the gap between the imperative and declarative
world. A replacement for the venerable
"just sudo apt install <anything>".
As a result of its design, it often causes unexpected behaviour.
This page is dedicated to explaining what its issues are and what
to use instead.
Installing packages by derivation name
Packages in Nix are usually bundled in attribute sets. Each
attribute name represents the name of a package. When installing
packages declaratively through NixOS or Home Manager, or when declaring
a package's dependencies, these are uniquely identified using their attribute
name. When installing packages via nix-env -i,
attribute names are ignored. Instead, nix-env traverses
the entire attribute set to find a package with a matching
derivation name. This can lead to fun surprises when
the derivation name does not match the attribute name, such as
installing an unwrapped package that requires a wrapper to function
properly. This can be avoided by using nix-env -iA
instead, which picks packages via attribute name, but does not
note down from which attribute path a package originally came from,
resulting in surprises when upgrading it.
{
pkg = < derivation pkg-wrapper-1.3 >;
pkg-unwrapped = < derivation pkg-1.3 >;
}
Name collisions when upgrading packages
nix-env -u will upgrade all packages in your profile
by searching through the attribute set for a derivation with the same
derivation name and a higher version number. When using large,
nested package collections like nixpkgs, derivations from different
language ecosystems may be stored under a distinct attribute path,
but their derivation name may be the same, despite the two packages
being clearly different otherwise.
{
zstd = < derivation zstd-2.0 >;
haskellPackages = {
zstd = < derivation zstd-3.0 >;
};
}
Unintentional major version jumps
Nixpkgs sometimes keeps multiple major versions for packages that have
multiple continuously maintained release trains, such as PostgreSQL.
Because the distinction between major versions is done via attribute
names, nix-env completely ignores it. When installing
postgresql_12 via its attribute name, you would
expect PostgreSQL to stay at major version 12, even when upgraded.
Even though postgresql_12 and
postgresql_14 may exist in parallel within the same
nixpkgs revision, nix-env ignores this fact and will
happily upgrade your PostgreSQL package to major version 14.
Performance issues
As you may have already guessed, iterating over a large package set and
evaluating every derivation in it is
not very efficient.
Non-obvious shadowing
nix-env installs packages into a user-specific
profile that has precedence over system-level directories in
$PATH. This means that you can install a
different version of any tool in your user profile without the rest of
the system having to use that version. If you forget that you installed
a program into your user profile like this, you may end up with a nasty
surprise later down the line.
For example, installing busybox this way would shadow
common utilities such as ls, cat or
grep. The next time you would try to use a GNU-specific
feature of grep months down the line, you might end up
going on a wild goose chase to figure out why your version of
grep behaves differently than everyone else's.
Implicit package pinning
Packages installed by nix-env are independent of the NixOS
or Home Manager configuration. This can be beneficial because NixOS
upgrades will never change what versions of packages a user has installed,
however it is a common source of confusion, as a system update
("apt upgrade") on regular distros will truly upgrade
all packages, while on NixOS or when using Home Manager, this is not the
case.
Informational Video by Matthew Croughan
Matthew Croughan demonstrates some of the issues with nix-env.
Alternatives
So, nix-env is bad. What do we use instead? First,
it's important to consider that Nix is a far more powerful package
manager than your garden variety ones like APT or DNF. As such, there
is more than one way to provide a package. Merely installing
the package is probably never your end goal. Consider what you want
to do with this package, and then choose how to expose the package
in the target environment appropriately.
Declarative package management
This is the best choice for any packages you expect to be
long-living. Any applications that you commonly use should be
managed in this way. Packages can be managed declaratively through
tools such as NixOS configuration or Home Manager. These will often
also provide a set of options to configure the application in Nix code.
System services should only be configured through these options,
which will automatically define a suitable systemd service according
to your specifications.
Ephemeral shell environments
Do you often run into a situation where you need a particular command
for a one-off thing, but don't feel like it should reside on your system
at all times?
Ephemeral shells
allow you to gain temporary access to a command and after you exit
out of the shell, it's as if the package was never installed.
If you're using Flakes,
nix shell
may be more up your alley.
nix profile
Lastly,
nix profile
from the Nix 3.0 (Flakes) set of CLI commands aims to provide a more
polished imperative package management solution. If you really need to
imperatively manage some of your packages, this is your best option.
It picks packages by attribute name rather than derivation name and it
keeps track of the attribute path from which each package was installed,
meaning name collisions when upgrading are eliminated. Thanks to Flakes,
it also allows you to easily install packages from package collections
other than nixpkgs.
Version 1.2.1 |
This is a living document. |
Contributions welcome