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 (systemd
required) 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
. :::
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 Nix development shell when entering the repository.
Both are provided automatically if the repository template is used.
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
Tips & Tricksβ
The following section always uses nix run "github:nixos/nixpkgs#tool" -- ...
to run a tool
directly over Nix (by building it directly into your Nix store
form nixpkgs
). This is for making the snippets self-contained. You can of
course remove this and just use tool
.
Inspect Derivationsβ
When working with Nix, it can be useful to inspect derivations.
Visualize Dependenciesβ
Use nix-tree to inspect visually the dependencies of your store path or installable:
-
Run
nix-tree
to visualize the dependencies of a Nix store path:nix run github:utdemir/nix-tree -- /nix/store/<path>
-
Run
nix-tree
to visualize the dependencies of toolcowsay
(i.e.github:nixos/nixpkgs#cowsay
is an installable):nix run "github:nixos/nixpkgs#nix-tree" -- [--derivation] "github:nixos/nixpkgs#cowsay"
List Store Path of Installablesβ
List store path of an (installable) like:
-
Run
nix eval
on an attribute pathprettier.outPath
on derivationprettier
to list the Nix store path when it gets build withnix build
:nix eval --raw "github:nixos/nixpkgs#cowsay.outPath"
> /nix/store/xkk1gr9bw2dbdjna8391rj1zl1l3dmhq-cowsay-3.8.4%or
nix derivation show "github:nixos/nixpkgs#cowsay.outPath" |
nix run "github:nixos/nixpkgs#jq" -- -r '.[] | .outputs.out.path'
Visualize Build Progressβ
The tool nix-output-monitor
(a.k.a
nom
) visualizes the output
of nix build
:
- Visualize the progress with
nom
when buildingpython
version3.8.3
(which is atnixpkgs
revisiondce8fc727dc2891628e4f878bb18af643b7b255d
, see nixhub.io):
nix build "github:nixos/nixpkgs/dce8fc727dc2891628e4f878bb18af643b7b255d#python38" \
--log-format internal-json -v |&
nix run "github:nixos/nixpkgs#nix-output-monitor" -- --json
You can use --option substitute false
on nix build
to force building it and
not using
substituter
(a.k.a Nix store path caches)
or
nix run "github:nixos/nixpkgs#nix-output-monitor" -- build \
"github:nixos/nixpkgs/dce8fc727dc2891628e4f878bb18af643b7b255d#python38"
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 = [ /*...*/ ]};
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. */};
}
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
) 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.
Language Guidelineβ
For more information on
nix
language and its toolchain read here.
Contactsβ
- Gabriel NΓΌtzi
- Cyril Matthey-Doret
- Eike Kettner