Skip to main content

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).

info

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.

info

Troubleshooting

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.

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 the flake.nix.
  • nix develop – For entering development shells defined in the flake.nix.
  • nix run – To run applications or commands defined in the flake.nix.
  • nix flake update – For updating dependencies in the flake.nix.
  • nix profile
    • Installing packages from a flake flake.nix and managing different profiles and package versions declaratively. – The modern alternative to nix-env.
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.

warning

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 with nix-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 using home-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 use nix-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 that nix-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 use nix-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:

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
note

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 executable exe into a new derivation pkgs.writeShellScriptBin where you use nix-alien to run the exe.

More information: Running Downloaded Binaries on NixOS.

General Concepts to Remind

Everything Is a Function

  • For example in nix repl --file test.nix:

    test.nix
    let
    # 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 function nixpkgs with an attribute set like { system = /*...*/; ... }:

  • For example, the outputs field on the attribute set in a flake.nix file is defined to be a function with one arguments input which

    flake.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.

tip

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:

flake.nix
{
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

References