Nix and NixOS
Nix is a package manager that emphasizes on reproducibility, isolation, and declarative system configuration. It is at the moment one of the few technologies which makes software distribution and packaging stable and deterministic.
The package manager nix
uses the Nix
functional programming language to
drive reproducibility. The Nix language resembles JSON with added functions.
Functional concepts (no side-effects etc.) make build processes for software
packages deterministic and isolated, ensuring that builds are reproducible
regardless of the system's current state.
Key Feature
-
Development Environments: Nix development shells (e.g.
nix develop
) enable developers to define the exact versions of tools and dependencies needed to build/test/lint/format a software project. This ensures that everyone working on the project is using the same environment.This is used to create lightweight, isolated environments without needing a full virtual machine or container image. This is in contrast to
.devcontainers
which uses container images. -
NixOS: A Linux distribution where the entire system (not just packages) is managed by Nix. Everything from packages to system configurations are declared in a single configuration file.
The package manager nix
comes with
nixpkgs
which is Nix's central package
repository and currently the largest package repository (> 100k packages).
Nix documentation is sometimes scattered and finding the right way to do something can be daunting and overwhelming. Therefore it is important to follow the development setup and guide in this documentation.
Starter Links:
Matrix Channels:
More Information
Key Features of Nix
-
Reproducibility: Since the build instructions and dependencies are declared in a Nix expression (a configuration file), software is built the same way every time, ensuring consistency across different machines.
-
Isolation: Each package is built in its own isolated environment, preventing dependency conflicts. Different versions of the same software can coexist without problems.
-
Declarative Configuration: Nix can describe the entire system configuration declaratively. For example, with NixOS (an operating system based on Nix), you can define everything from installed packages to system services in a single configuration file.
-
Atomic Upgrades and Rollbacks: Nix supports atomic upgrades and easy rollbacks. This means you can upgrade a package (or the entire system) and, if something breaks, easily roll back to the previous state.
-
Cross-Platform: While commonly associated with Linux and NixOS, Nix can also be used on macOS and to some extent on Windows via WSL (Windows Subsystem for Linux).
Use Cases
-
NixOS: A Linux distribution where the entire system (not just packages) is managed by Nix. Everything from packages to system configurations are declared in a single configuration file.
-
Development Environments: Nix is popular for creating reproducible development environments. Developers can define the exact versions of tools and dependencies needed, ensuring that everyone working on the project is using the same environment.
-
Containerization: Nix can be used to create lightweight, isolated environments without needing a full virtual machine or Docker.
Installing Nix
If you have not installed nix
on your Linux (with systemd
) or macOS machine
(consult the documentation for other environements) use
nix-installer
This installer will activate also the newer Nix CLI commands and enable Nix Flakes.
Installing Direnv
The tool direnv
executes stuff when entering a
directory in the shell.
When working in nix-ified projects (where you find a flake.nix
) its best to
also have a .envrc
file in the root of the project to make users of your
project directly drop into Nix development shell.
- Install direnv.
- Also setup
direnv
for your shell. This enables you to just enter (e.g.cd repo
) a directory anddirenv
will auto-source.envrc
. A default.envrc
which loads a Nix development shell can be found here.
Troubleshooting
- To disable too much verbose output in
direnv
use these settings.
How Nix Should Be Used
The Nix package manager and the Nix language in their traditional forms
lack explicit support for package pinning, which ensures reproducibility
across different environments over time. In contrast, Nix Flakes (e.g.
flake.nix
) provides a built-in mechanism for precise pinning of
dependencies.
- Use a
flake.nix
in a repository where you want Nix tooling. - Use a
.envrc
file which will load a dev. shell when entering the repository.
In summary, when using flakes, you should move away from legacy commands like
nix-env
, nix-shell
, and nix-channel
, and instead use flake-compatible
commands like nix build
, nix develop
, and nix run
to maintain
reproducibility and consistency:
nix build
– For building packages or outputs defined in theflake.nix
.nix develop
– For entering development shells defined in theflake.nix
.nix run
– To run applications or commands defined in theflake.nix
.nix flake update
– For updating dependencies in theflake.nix
.nix profile
- Installing packages from a flake
flake.nix
and managing different profiles and package versions declaratively. – The modern alternative tonix-env
.
- Installing packages from a flake
Flakes
With Nix Flakes (e.g. flake.nix
):
- You can explicitly declare the exact versions or commits of package sets and
other resources (like
nixpkgs
, binary caches, or other flakes) that your project depends on. - This ensures that your environment remains consistent, even if the upstream packages evolve or break over time.
- Additionally, flakes handle dependency resolution in a more declarative and immutable fashion, meaning that you can always reproduce your environment on different machines without manual intervention.
Thus, while the traditional Nix package manager and language offer flexibility,
flake.nix
offers a more robust, first-class mechanism for ensuring
reproducibility through automatic package pinning.
Also flake.nix
is not a stable nix
feature, it (or a similar approach) will
become stable over time. It is too widely used, and the new Nix governance also
points in this direction.
Using Nix Flakes etc. means nix-env
commands must not be used..
Details
The primary reasons to avoid using nix-env
in NixOS or when working with Nix
are related to reproducibility, user environment management, and the
availability of better alternatives.
Here are some reasons why you should not want to use nix-env
:
Lack of Reproducibility
nix-env
operates on the user profile, and changes made with it are often not reproducible across systems. If you install packages withnix-env
, they are added to your user environment but aren't easily tracked or shared with others in the form of a declarative configuration.- This breaks one of the core strengths of Nix — reproducibility and immutability of environments.
Declarative Configuration Is Better
- A better alternative to
nix-env
is managing your environment declaratively usinghome-manager
for user environments or the NixOS configuration for system-level changes. - Using these tools, you define your package setup in configuration files, making it easier to version control your environment and share it between systems.
Potential for "Garbage" Accumulation
nix-env
installs software for the current user and can lead to accumulation of old packages or dependencies because it doesn’t always clean up old generations well.nix-collect-garbage
helps clean up unused packages, but if you usenix-env
frequently without understanding the side effects, your store might fill up with unnecessary items.
Nix Profile Management Is Smoother
- Profiles in Nix (using
nix profile
in newer versions of Nix) provide more intuitive ways to manage environments. They support multiple, independent profiles for different use cases and avoid the confusion thatnix-env
may introduce. - Profiles also offer better control over rollback and isolation.
Unclear Upgrade Paths
nix-env
lacks the precision of package version management. When you usenix-env -u
to upgrade packages, you might end up upgrading packages that you didn’t want to upgrade or miss upgrading packages you intended to.
In summary, while nix-env
may seem convenient for ad-hoc package management,
it often goes against the grain of Nix's philosophy of reproducibility and
declarative configuration. Using alternatives like nix profile
,
home-manager
, or NixOS configurations can lead to cleaner, more maintainable,
and reproducible setups.:
Tutorial
To not repeat the documentation here are some great tutorial you should watch:
- Video: Nix Language Explained
- Video: Nix Flake Guide
- Video: Nix Flake Inputs
- Zero-To-Nix
- NixOS & Flakes Book
Running Unpatched Binaries
The Nix ecosystem is such that all binaries which are build with Nix link to
libraries in the Nix store (e.g. /nix/store
).
Details
You can check that by doing e.g.
nix build nixpkgs#hello
ldd ./result/bin/hello
# Delete the build output again.
# rm -rf ./result
Output:
linux-vdso.so.1 (0x00007ffd94778000)
libc.so.6 => /nix/store/3dyw8dzj9ab4m8hv5dpyx7zii8d0w6fi-glibc-2.39-52/lib/libc.so.6 (0x00007f0754e6b000)
/nix/store/3dyw8dzj9ab4m8hv5dpyx7zii8d0w6fi-glibc-2.39-52/lib/ld-linux-x86-64.so.2 => /nix/store/3dyw8dzj9ab4m8hv5dpyx7zii8d0w6fi-glibc-2.39-52/lib64/ld-linux-x86-64.so.2 (0x00007f0755064000)
Some executable binaries (e.g. which are installed over certain tools which
circumvent the Nix store e.g. npm
, yarn
etc.) may have dependencies to
libraries that are not available in the Nix package manager,
or may require patching or modification to work correctly on the operating system.
Thats where nix-ld
helps out.
Precompiled binaries that were not created for NixOS usually have a so-called link-loader hardcoded into them. On Linux/x86_64 this is for example /lib64/ld-linux-x86-64.so.2. for glibc. NixOS, on the other hand, usually has its dynamic linker in the glibc package in the Nix store and therefore cannot run these binaries. Nix-ld provides a shim layer for these types of binaries.
-
On NixOS: Using
nix-ld
in NixOS together with some development shell settings. -
On NixOS or with Nix: Using
nix-alien
enables to run patched binaries. Its also possible to wrap an executableexe
into a new derivationpkgs.writeShellScriptBin
where you usenix-alien
to run theexe
.
More information: Running Downloaded Binaries on NixOS.
General Concepts to Remind
Everything Is a Function
-
For example in
nix repl --file test.nix
:test.nixlet
# Imports the `nixpkgs` function form Github.
nixpkgs = import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/refs/tags/24.05.tar.gz")
in
nixpkgs {
inherit system;
overlays = [];
};the
nixpkgs
get instantiated by calling a functionnixpkgs
with an attribute set like{ system = /*...*/; ... }
: -
For example, the
outputs
field on the attribute set in aflake.nix
file is defined to be a function with one argumentsinput
whichflake.nix{
inputs = { /* Input attribute set. */ };
outputs = inputs: {/* Output attribute set. */};
}
Version Pinning
Version pinning (e.g. in a flake.nix
) normally happens by requiring the
nixpkgs
-monorepo on a certain version
where that required derivation had the version in question. The collection
nixhub.io helps in finding particular versions for
derivations.
To pin for example python
at version 3.13.2
, you search on
nixhub.io and get the following reference
573c650e8a14b2faa0041645ab18aed7e60f0c9a#python313
.
The first part is the commit SHA1 of the
nixpkgs
-monorepo.
You can import this in the flake.nix
similar to this:
{
inputs = {
nixpkgs-python-3-12-2 = "github:nixos/nixpkgs/573c650e8a14b2faa0041645ab18aed7e60f0c9a"
}
outputs = {self, nixpkgs-python-3-12-2}:
let
system = "x86_64-linux";
pkgs = nixpkgs-python-3-12-2.legacyPackages.${system};
in
{
devShells.${system}.default = pkgs.mkShell {
packages = [ pkgs.python313 ];
}
}
}
If you need to configure overlays and adjust other thing you would import
nixpkgs-python-3-12-2
which is a more costly operation by doing instead:
pkgs = import nixpkgs-python-3-12-2 { inherit system; overlays = [ /*...*/ ]};
Language Guideline
For more information on
nix
language and its toolchain read here.
Contacts
- Gabriel Nützi
- Eike Kettner