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
.devcontainerswhich 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
direnvfor your shell. This enables you to just enter (e.g.cd repo) a directory anddirenvwill auto-source.envrc.
Troubleshootingβ
- To disable too much verbose output in
direnvuse 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.nixin a repository where you want Nix tooling. - Use a
.envrcfile 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.nixand 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-envoperates 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-envis managing your environment declaratively usinghome-managerfor 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-envinstalls 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-garbagehelps clean up unused packages, but if you usenix-envfrequently without understanding the side effects, your store might fill up with unnecessary items.
Nix Profile Management Is Smootherβ
- Profiles in Nix (using
nix profilein 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-envmay introduce. - Profiles also offer better control over rollback and isolation.
Unclear Upgrade Pathsβ
nix-envlacks the precision of package version management. When you usenix-env -uto 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:
- Nix Workshop: Slides & Exercises
- 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-treeto visualize the dependencies of a Nix store path:nix run github:utdemir/nix-tree -- /nix/store/<path> -
Run
nix-treeto visualize the dependencies of toolcowsay(i.e.github:nixos/nixpkgs#cowsayis 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 evalon an attribute pathprettier.outPathon derivationprettierto 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
nomwhen buildingpythonversion3.8.3(which is atnixpkgsrevisiondce8fc727dc2891628e4f878bb18af643b7b255d, 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"
Garbage Collectionβ
Nix garbage collection works by removing store paths (/nix/store/...) that are
no longer reachable from any GC root. A GC root is simply a reference that
"pins" a derivation or build output so it won't be collected β for example,
system generations in /nix/var/nix/profiles, Home Manager generations, or
manually created roots via nix build --out-link.
When you run nix store gc, Nix traverses all known roots and deletes any store
paths not referenced by them.
If something is still reachable from a GC root, it will not be deleted, even if you don't need it anymore.
You can use the old command nix-store --gc --print-dead to the above commands
to list the all non-reachable store paths.
Conversely, if you manually remove a root (like an old generation), its store paths become collectible. This means understanding and managing roots is key to controlling what survives garbage collection.
Garbage Collect Dev Shellsβ
It is safe to remove all .devenv folders in repositories arising from Nix
shells with devenv.sh. These folders contain symlinks and
GC roots to /nix/store/... paths. When removed, nix store gc will see all
references Nix store paths in this .devenv folder as unused and purges them.
cd repo
rm .direnv
rm .devenv
nix store gc
To nuke all Dev shells do
find ./ -type d -path "*/.devenv" -or -path "*/.direnv" -exec echo rm -rf {} \;
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
nixpkgsget instantiated by calling a functionnixpkgswith an attribute set like{ system = /*...*/; ... }: -
For example, the
outputsfield on the attribute set in aflake.nixfile is defined to be a function with one argumentsinputwhichflake.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-ldin NixOS together with some development shell settings. -
On NixOS or with Nix: Using
nix-alienenables to run patched binaries. Its also possible to wrap an executableexeinto a new derivationpkgs.writeShellScriptBinwhere you usenix-aliento 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