NixOS
NixOS is a GNU/Linux distribution centered on the Nix package manager, which employs a purely functional approach to package management and system configuration, allowing users to define the entire operating system state declaratively in a single configuration file for reproducible builds and atomic upgrades.[1] Developed initially as a research project by Eelco Dolstra during his PhD at Utrecht University, Nix—the foundational package manager—was first described in Dolstra's 2006 thesis, with NixOS emerging as an extension to apply these principles to full system configuration around 2003–2008.[2][3][4] Key features include isolation of packages to prevent dependency conflicts, support for rollbacks to previous system generations via the bootloader, and, as of November 2025, over 120,000 software packages available through the community-maintained Nixpkgs repository, enabling consistent environments across development, testing, and production.[1][5] Now community-driven under the NixOS Foundation, it emphasizes modularity through a system of Nix modules for services, users, and hardware, facilitating easy customization and multi-user support without traditional package manager pitfalls like partial upgrades.[1][3]Introduction
Definition and Core Principles
NixOS is an independently bootable Linux distribution constructed entirely around the Nix package manager, which enables purely functional deployment of software and system configurations.[1][6] This approach treats the operating system as a collection of immutable components derived from declarative specifications, ensuring that the entire system can be rebuilt deterministically from source definitions.[1][6] At its core, NixOS adheres to principles of immutability, declarativity, and isolation. Immutability ensures that system states and packages remain unchanged once created, allowing multiple versions to coexist without interference.[1][6] Declarative descriptions of the entire system are expressed using Nix expressions, a functional language that defines the desired configuration rather than imperative steps to achieve it.[1][6] Isolation prevents dependency conflicts by storing packages in unique paths within the Nix store, such as/nix/store, enabling side-by-side installations of different library versions.[1][6]
These principles yield high-level benefits including predictable system behavior, simplified testing of configurations in isolated environments, and enhanced portability across diverse machines.[1][6] For instance, the entire operating system configuration can be defined in a single file, such as configuration.nix, which is evaluated by the Nix package manager to generate a complete, reproducible system.[1]
Relationship to the Nix Package Manager
Nix serves as the foundational package manager and build system for NixOS, employing the Nix Expression Language to define packages in a purely functional manner, where each package is built in isolation and identified by a unique cryptographic hash to ensure reproducibility and prevent undeclared dependencies.[7] This hashing mechanism stores all packages in the Nix store, typically at/nix/store, enabling multiple versions to coexist without conflicts. NixOS leverages these core principles to manage not just user-level software but the entire operating system, including the kernel, bootloader, and system services.[8]
NixOS extends Nix by treating the complete system configuration as a single, derivable Nix expression, allowing the entire OS to be reconstructed deterministically from source definitions. This is conventionally achieved through the /etc/nixos/configuration.nix file, though NixOS supports building from any valid Nix expression including those defined in flakes or remote sources, where administrators declaratively specify components such as installed packages, user accounts, and network settings using Nix syntax.[9] Unlike standalone Nix installations on other Linux distributions, which primarily handle ad-hoc user environments via tools like nix-env, NixOS integrates Nix deeply into system initialization, ensuring that all elements align with the central configuration for holistic management.[10]
A key adaptation in NixOS is its module system, which builds upon Nix's extensibility to provide structured definitions for services and hardware, such as enabling systemd units or configuring the GRUB bootloader, all evaluated and merged from the Nixpkgs repository.[11] Nixpkgs, the canonical collection of over 120,000 Nix packages, supplies the building blocks for NixOS components, including OS-specific modules that facilitate integration with tools like systemd, distinguishing NixOS from Nix's more limited role on non-NixOS systems.[12] This architecture enables atomic system activations, where changes are applied only if the build succeeds, maintaining system integrity.[8]
History
Origins in Research
NixOS originated in 2003 as part of Eelco Dolstra's PhD research at Utrecht University, where he developed the foundational concepts for a purely functional approach to software deployment.[13] This work built upon the newly initiated Nix package manager, also started that year by Dolstra, which introduced a model for reproducible and isolated package installations using cryptographic hashing to avoid traditional dependency conflicts. Supervised by Eelco Visser as copromotor, Dolstra's thesis, titled The Purely Functional Software Deployment Model and defended in 2006, outlined extending Nix to encompass an entire operating system, aiming to create reliable deployments free from configuration drift and the "dependency hell" plaguing conventional Linux distributions.[13] The initial focus of NixOS was to transform Nix's package-level functionality into a comprehensive Linux distribution, enabling declarative system-wide configurations that ensured reproducibility—a core principle shared with Nix itself.[13] Early development emphasized solving issues like inconsistent environments across machines and manual configuration errors by treating the entire system as a functional composition of components stored in the immutable/nix/store.[13]
Key early collaboration came from Armijn Hemel, who contributed significantly to the Nixpkgs repository—the collection of Nix expressions for building packages—and initiated the first prototype of NixOS as part of his 2006 master's thesis at Utrecht University.[13][14] Hemel's work helped operationalize the research vision by applying Nix to manage a bootable Linux system, including services and kernels.[14]
The first conceptual description of NixOS appeared in 2006, framed in Dolstra's PhD thesis as a "purely functional Linux distribution" that deploys all system components functionally, without mutable state changes during upgrades or installations.[13] This publication solidified the project's academic roots, highlighting its potential to achieve atomic, rollback-capable system management.[13]
Evolution and Key Milestones
NixOS transitioned to fully open-source development with the establishment of its public GitHub repositories in the NixOS organization, enabling community contributions starting around 2010. The project saw its first alpha releases emerge in late 2012, marking the shift from research prototype to a viable distribution accessible for testing and feedback. This period laid the groundwork for broader adoption by integrating NixOS configurations into the nixpkgs repository, which by then already hosted thousands of packages.[12] A pivotal milestone came in 2013 with the release of NixOS 13.10 "Aardvark," recognized as the inaugural stable branch and providing the foundation for reliable system deployments. Building on this, the 2014 release of NixOS 14.04 "Baboon" introduced enhancements to the core module system, improving declarative configuration capabilities and solidifying the distribution's architectural stability. These early stable releases facilitated the maturation of NixOS from an experimental project to a production-ready Linux distribution.[15] The distribution entered a growth phase in 2015 with the formation of the NixOS Foundation, a nonprofit entity dedicated to sustaining infrastructure, documentation, and structured releases to support the expanding community. By 2017, NixOS adopted a biannual release cycle, aligning versions with May and November timelines to balance innovation with stability, as evidenced by releases like 17.03 "Gorilla" and 17.09 "Hummingbird." This cadence accelerated development and user adoption, with post-2020 community efforts driving scalability through improved governance and collaboration tools.[16][17] In 2021, the introduction of Flakes via Nix 2.4 integration represented a major advancement in reproducibility, standardizing project dependencies and outputs to reduce evaluation inconsistencies across environments. The NixOS Foundation further evolved its governance structure around this time, emphasizing community-driven initiatives amid rising interest. By November 2025, nixpkgs encompassed over 120,000 packages, underscoring the ecosystem's scale. Industry adoption highlighted this maturity, notably by IOHK for deploying the Cardano blockchain infrastructure, leveraging NixOS for reproducible and atomic updates across hundreds of nodes.[18][12][19] In 2024, the project faced a significant governance crisis triggered by disputes over sponsorships from defense-related companies for NixCon events, raising ethical concerns within the community about project alignment and trademark usage. This led to resignations, including a temporary step-down by founder Eelco Dolstra as Foundation director, and intense discussions on inclusivity, leadership accountability, and formalizing governance. The crisis was resolved through community engagement, Dolstra's return, adoption of a new constitution in late 2024, and enhanced transparency measures. The biannual release cycle continued uninterrupted, with NixOS 24.11 released in November 2024 and 25.05 "Warbler" in May 2025, incorporating improvements in stability and package support. As of November 2025, ongoing efforts addressed further moderation team challenges, reinforcing the community's commitment to collaborative evolution.[20][21]Release Management
Versioning and Cycle
NixOS maintains a biannual release cycle for its stable versions, with releases scheduled for late May and late November each year.[17] This structured cadence ensures regular updates while allowing time for testing and stabilization.[22] The cycle, which marked a historical shift from earlier irregular schedules, was established around 2017 to provide more predictable support for users.[23] Stable releases follow a versioning scheme in the YY.MM format, where YY represents the last two digits of the release year and MM indicates the month (05 for May or 11 for November), such as 25.05 for the 2025 May release.[24] Each version is assigned an animal codename in alphabetical order, for example, "Warbler" for 25.05 and "Vicuña" for 24.11, to facilitate easy reference and theming.[17] Support for each stable release lasts seven months, encompassing security patches and critical bug fixes, with end-of-support dates aligned to December 31 for May releases and June 30 for November releases.[17] As of 2025, the 25.05 "Warbler" release was issued on May 23, 2025, and remains supported until December 31, 2025.[25][17] In parallel, an unstable branch tracks ongoing development, pulling from the master branch of the nixpkgs repository to enable early testing of new packages, features, and configurations before their integration into stable releases.[24] This branch receives frequent updates but is not recommended for production environments due to potential instability.[26] The release process begins with a feature freeze several weeks prior, followed by a branch-off from the nixpkgs repository to create the dedicated release branch, typically one week before the target date.[22] Post-release, maintenance focuses on backported security updates and fixes to the stable branches, avoiding new features to preserve compatibility and reliability.[26] Backports are applied via pull requests targeted at the specific release branch, such as release-25.05, ensuring targeted enhancements without broad rebuilds.[26] This approach leverages the Nix package manager's strengths in reproducibility and atomicity to deliver secure, minimal interventions throughout the support window.[22]Major Release Highlights
NixOS 18.03, codenamed "Impala", marked a significant milestone as the ninth stable release branch, launched on April 4, 2018. This version introduced Nix 2.0 by default, which brought enhancements to multi-user support and overall package management stability. Key improvements included better hardware compatibility through an upgrade to Linux kernel 4.14, enabling support for newer GPUs and network devices. The installer saw refinements for easier initial setup, including improved configuration options during installation. Additionally, it was the first NixOS release to ship with GNOME 3.26, providing updated desktop features like enhanced Wayland integration and usability tweaks.[27][28] The 21.11 release, known as "Porcupine", arrived on November 30, 2021, and introduced experimental support for Nix Flakes, a new mechanism for managing dependencies and ensuring reproducible builds in a more structured way. This integration allowed users to experiment with flake-enabled configurations directly in NixOS, paving the way for more hermetic and portable system definitions. Container support was notably enhanced, with better integration for tools like Podman, including rootless operation and systemd-managed services for streamlined deployment of containerized applications. These changes contributed to improved isolation and ease of use for development and production environments.[29][21] NixOS 23.11, dubbed "Tapir", was released on November 29, 2023. It included Nix 2.18 and continued support for ARM64 through ongoing kernel work. These updates improved cross-architecture portability and command-line ergonomics, facilitating smoother transitions for users on diverse hardware.[30][31] In November 2024, NixOS 24.11 "Vicuña" was released on November 30, 2024. It added numerous new packages and modules, with updates to GNOME 47 and KDE Plasma 6.2.[32][21] The most recent major release, NixOS 25.11 "Xantusia", was released on November 30, 2025. It remains supported until June 30, 2026.[33][21] Each NixOS major release incorporates hundreds of package updates, alongside new modules and services, reflecting the community's ongoing contributions; comprehensive details are available in the official release notes within the NixOS manual.[21]Core Features
Declarative Configuration Model
NixOS employs a declarative configuration model in which the entire desired state of the system—including packages, services, users, and hardware settings—is specified in a single, human-readable Nix expression, typically located at/etc/nixos/configuration.nix.[34] This file is evaluated by the Nix evaluator to generate all static components of the operating system, such as the contents of /etc, running services, and installed software, ensuring that the system precisely matches the declared specification without relying on imperative commands or manual file edits.[4] The model draws from purely functional programming principles, treating system configuration as a function that produces immutable outputs stored in the Nix store, thereby avoiding state-dependent modifications common in traditional Linux distributions.[4]
Central to this model is the NixOS module system, which allows configurations to be composed from reusable, modular components. Each module defines a set of options—typed declarations for system aspects like enabling a service or setting a network parameter—and provides default values or derivations that implement those options. For instance, options such as services.nginx.enable = true; or users.users.alice.isNormalUser = true; can be set within the configuration, and the module system merges them hierarchically during evaluation to form a cohesive system derivation.[35] Modules can be imported from separate files (e.g., via imports = [ ./hardware.nix ];), promoting modularity and enabling complex setups to be built incrementally without duplication.[35]
This approach offers several advantages, including the ability to version-control entire system configurations as code, facilitating testing through dry-run evaluations (nixos-rebuild dry-run), and eliminating the need for post-build manual interventions since all changes are applied atomically from the declaration.[34] Configurations are inherently human-readable and composable, reducing errors from imperative scripting, and they support reproducibility across machines by pinning dependencies to specific versions.[4]
A representative example of declarative configuration involves defining users, system packages, and firewall rules in a unified expression:
Applying this with{ config, pkgs, ... }: { users.users.example = { isNormalUser = true; extraGroups = [ "wheel" ]; packages = [ pkgs.vim pkgs.git ]; }; environment.systemPackages = [ pkgs.firefox ]; networking.firewall = { enable = true; allowedTCPPorts = [ 80 443 ]; }; }{ config, pkgs, ... }: { users.users.example = { isNormalUser = true; extraGroups = [ "wheel" ]; packages = [ pkgs.vim pkgs.git ]; }; environment.systemPackages = [ pkgs.firefox ]; networking.firewall = { enable = true; allowedTCPPorts = [ 80 443 ]; }; }
nixos-rebuild switch builds and activates the system atomically, installing the specified packages, creating the user, and configuring the firewall without partial states or manual steps.[36]
Atomic Upgrades and Rollbacks
One of the defining features of NixOS is its support for atomic upgrades, which ensure that system changes are applied in an all-or-nothing fashion, preventing partial or inconsistent states. When a user modifies the system configuration—typically defined in a declarative file such as/etc/nixos/configuration.nix—the nixos-rebuild switch command evaluates, builds, and activates the new configuration as a single transaction. This process isolates the build in the Nix store, creating a complete new system profile without altering the running system until activation succeeds. If any step fails, such as due to compilation errors or dependency issues, the original configuration remains intact, and the system continues operating unchanged.[37]
The activation phase updates the system's profile symlink in /nix/var/nix/profiles/system to point to the new generation, while preserving all prior versions. This profile manages the system's bootable state, including kernel, initrd, and essential binaries, ensuring that the switch is instantaneous and reversible. NixOS integrates this with bootloaders like GRUB or systemd-boot, which automatically list available generations in the boot menu for selection at startup. As a result, even interruptions like power failures leave the system bootable into a fully consistent state, either the old or the new configuration.[6]
Rollbacks in NixOS leverage these generations for straightforward reversion to previous states. The command nixos-rebuild switch --rollback switches the active profile back to the immediately prior generation, updating the bootloader menu accordingly without requiring manual file restoration or complex recovery procedures. Generations accumulate indefinitely until manually pruned using tools like nix-collect-garbage or nix-env --delete-generations, which remove old profiles from /nix/var/nix/profiles/system after confirming no references remain. This mechanism allows users to experiment with configurations confidently, knowing they can revert atomically if issues arise, such as service incompatibilities or performance regressions.[37]
Reproducible Builds and Configurations
NixOS achieves reproducibility in builds through its use of derivations, which are formal descriptions of build processes that include all inputs, ensuring that the same derivation always produces the same output. Each derivation generates a unique store path in the Nix store, prefixed with a content-addressed hash, such as/nix/store/sha256-<hash>-<name>, where the hash is computed from the cryptographic hash of all build inputs, including dependencies, source code, and build scripts. This hashing mechanism guarantees that identical inputs yield identical hashes and thus bit-for-bit identical build outputs across different machines and environments, provided the builds are performed in Nix's sandboxed isolation to prevent external influences like timestamps or host-specific data from affecting results.[38][39]
The declarative nature of NixOS configurations further extends this reproducibility to entire system setups. A Nix expression, such as the configuration.nix file, defines the complete system state, including packages, services, and settings; when evaluated with the same inputs (e.g., pinned versions of nixpkgs), it produces an identical system derivation on any compatible machine, allowing users to recreate exact environments from source without variation. This eliminates issues common in traditional Linux distributions, where manual installations or differing host configurations lead to inconsistencies.[5][1]
To facilitate testing and verification, Nix provides tools like nix-build, which instantiates and realizes derivations while supporting options such as --check to rebuild and compare outputs for reproducibility, ensuring bit-for-bit matches. Binary caches complement this by distributing pre-built derivation outputs; Nix fetches artifacts only if their hashes match the required derivation, preserving reproducibility without necessitating full local rebuilds, as the cached content is verified against the expected hash. These features make NixOS particularly suitable for continuous integration and continuous deployment (CI/CD) pipelines, where reproducible builds enable reliable testing and deployment across distributed systems.[40][41][42]
Multi-User Support
NixOS supports multi-user package management through isolated per-user profiles, allowing each user to maintain their own set of installed packages without interfering with others. These profiles are stored in/nix/var/nix/profiles/per-user/<username>, providing user-specific environments that link to paths in the Nix store. In contrast, a global system profile manages shared components accessible to all users, ensuring consistency for system-wide software while preserving individual customizations.[37]
The system distinguishes between single-user and multi-user installation modes for Nix. In single-user mode, the Nix store is owned by a single user, limiting shared access, whereas multi-user mode employs a daemon process running as root to coordinate operations across users. NixOS defaults to the multi-user setup, which includes enabling the Nix daemon service during installation, facilitating secure and concurrent package management for multiple accounts.[1]
Security in multi-user NixOS is enhanced by running builds under dedicated non-root accounts, such as nixbld1 through nixbld32, rather than the invoking user's privileges. The Nix store and database are owned by root, with access controlled via group permissions on the daemon socket, preventing unauthorized modifications and isolating potentially untrusted builds. This mechanism ties into the broader Nix store isolation, where paths are immutable and content-addressed.
This configuration enables collaborative environments on NixOS, where non-privileged users can install and manage packages independently using commands like nix-env for profile-based installations or nix-shell for temporary environments, all without requiring sudo access.[37]
Advanced Capabilities
Flakes for Dependency Management
Flakes represent an experimental feature in the Nix package manager, introduced in version 2.4 on November 1, 2021, to establish a uniform structure for packaging and sharing Nix-based projects in a reproducible and discoverable manner.[43] This structure addresses longstanding challenges in dependency management by allowing projects to explicitly declare their inputs and outputs through a dedicatedflake.nix file, accompanied by a flake.lock file that pins exact versions of dependencies to prevent drift across builds or environments.[44] Originally proposed to enhance composability and consistency, flakes enable developers to treat entire projects, including NixOS configurations, as self-contained units that can be easily referenced and integrated.[45]
At the core of a flake is the flake.nix file, which defines an attribute set of inputs—such as the nixpkgs repository—and a function that produces outputs based on those inputs, tailored to specific systems like x86_64-linux. For instance, inputs might specify a URL like nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";, while outputs could include packages, NixOS modules, or development shells.[44] The flake.lock ensures reproducibility by recording cryptographic hashes and revisions, such as Git commits, for each input, allowing identical evaluations regardless of when or where the flake is instantiated.[44] To use flakes, the flakes and nix-command experimental features must be enabled in the Nix configuration, typically via nix.settings.experimental-features = [ "nix-command" "flakes" ]; in NixOS setups.[46]
In NixOS, flakes facilitate dependency management for system configurations by pinning external resources like channels or overlays, promoting reproducible deployments across machines or teams.[47] A common pattern involves defining a flake that inputs nixpkgs and outputs a nixosConfigurations attribute, which can then be built and activated with commands like nixos-rebuild switch --flake .#hostname.[47] This approach resolves versioning ambiguities inherent in traditional channel-based workflows, where unspecified revisions could lead to inconsistencies.[44]
The primary benefits of flakes include solving input versioning issues through declarative locking, which enhances reliability for collaborative projects, and supporting specialized outputs like development shells for isolated testing without altering global environments.[44] They also enable overlays for customizing packages and apps for direct execution, streamlining workflows in multi-user or distributed NixOS installations.[46] As of 2025, despite their experimental status, flakes have become essential for modern NixOS configurations, with widespread adoption in community setups and tools like the Determinate Nix Installer, which enables them by default.[46]
Nix-Shell for Isolated Environments
Nix-shell is a command-line tool in the Nix ecosystem that enables users to create temporary, isolated shell environments equipped with specific dependencies, without requiring a permanent installation on the system. By invokingnix-shell -p <packages>, it builds and provides access to the listed packages in the shell's $PATH, allowing immediate use while keeping the global system configuration unchanged. This functionality is particularly valuable in NixOS, where the system-wide Nix package manager ensures that dependencies are fetched from the Nix store or binary caches, enabling rapid setup without altering the host environment.[48]
The tool supports declarative environment definitions through a shell.nix file, which specifies a Nix expression for the desired build environment, including custom build inputs, environment variables, and initialization scripts via the shellHook attribute. For instance, a simple shell.nix might define { pkgs ? import <nixpkgs> {} }: pkgs.mkShell { buildInputs = [ pkgs.python3 pkgs.prettytable ]; shellHook = ''echo "Python development shell ready"''; }, and running nix-shell in that directory launches the configured shell. This integration facilitates reproducible setups for tasks such as scripting and prototyping, where users can test code in a controlled context that mirrors production dependencies. In impure mode, activated with the --impure flag, nix-shell permits access to external mutable paths or non-Nix tools, bridging gaps for hybrid workflows while maintaining isolation by default.[48][49]
Nix-shell's design emphasizes reproducibility in development workflows by leveraging Nix's pure evaluation model; the --pure option clears inherited environment variables, ensuring consistent behavior across machines and sessions. It is commonly used for software development, where developers can spawn environments with language runtimes, libraries, and tools on demand, and for testing scripts that require specific versions without risking system-wide changes. On NixOS, this aligns seamlessly with the declarative system paradigm, providing instant, version-pinned environments that enhance portability and reduce setup friction in collaborative projects.[48]
Since 2021, nix-shell has evolved alongside the introduction of Flakes through the modern nix develop command in Nix 2.4's new CLI, which supports flake-based development shells for enhanced dependency pinning and reproducibility. This progression allows flake inputs to define shell environments directly, extending nix-shell's capabilities for more structured, input-locked workflows without disrupting its core isolated usage.
Technical Implementation
The Nix Store Mechanism
The Nix store serves as the central repository for all immutable data in the Nix package management system, including software packages, libraries, and derived artifacts, ensuring that once an item is added, it cannot be altered or removed without explicit permission. Located at the/nix/store directory, every store path follows the structure /nix/store/<hash>-<name>, where <hash> is a unique cryptographic identifier—typically a truncated SHA-256 hash encoded in base-32—and <name> provides a human-readable description of the item, such as its package name and version. This hashing mechanism derives from the Nix expression's dependencies and build inputs, guaranteeing that identical inputs always produce the same path, while differing inputs yield distinct ones, thereby enforcing content-addressed storage and preventing duplicates.[50]
The immutability of store paths is fundamental to Nix's design, as no writable operations are permitted within /nix/store; any update requires creating a new path with a revised hash, which isolates dependencies and avoids global side effects across the system. This approach enforces build purity by confining each package to its own directory, eliminating risks from shared mutable state and ensuring that runtime environments reference exact, fixed versions of libraries and binaries. Consequently, Nix prevents "DLL hell"—the conflicts arising from incompatible library versions overwriting each other in traditional systems—by allowing multiple package variants to coexist indefinitely without interference, as each maintains its own isolated dependencies.[6]
To manage storage efficiency, Nix employs garbage collection, which identifies and removes unused paths based on a graph of dependencies tracked via "roots"—references from active system or user configurations that prevent deletion of required items. The primary tool for this is nix-collect-garbage, an alias for nix-store --gc, which scans the store and deletes all unreachable objects, reclaiming disk space while preserving paths linked by roots. For more aggressive cleanup, the -d option first invalidates old profile generations before garbage collection, ensuring that obsolete but retained versions do not block removal; users can also specify generations or time thresholds (e.g., older than 30 days) to fine-tune the process.[51]
Profiles integrate with the store by using symlinks in /nix/var/nix/profiles to reference active generations, enabling atomic management of user environments without directly altering the store. For instance, a profile directory might contain symlinks like default pointing to default-5-link, which in turn resolves to a specific store path such as /nix/store/...-user-environment, representing a snapshot of installed packages. This structure supports multi-user setups by segregating profiles per user (e.g., /nix/var/nix/profiles/per-user/<username>), allowing independent configurations while sharing the underlying immutable store.[52]
Source-Based Building with Binary Caches
In Nix, derivations serve as the core mechanism for defining build processes, expressed through thederivation function in the Nix language. This function takes an attribute set specifying essential elements such as the package name, target system, builder executable, and arguments, which collectively determine the inputs required for the build. The outputs are store paths in the Nix store, hashed based on the derivation's inputs to ensure uniqueness and immutability. Fixed-output derivations, a specialized type, are particularly used for fetching external resources like source tarballs, where the output hash is predefined to guarantee that the fetched content matches expectations without requiring a full build.
This source-based approach underpins Nix's commitment to purity and reproducibility, as every derivation isolates the build environment, preventing external influences and ensuring identical inputs produce identical outputs across machines. By default, Nix evaluates derivations to construct packages from source code, leveraging dependencies also stored in the Nix store for consistency.
To enhance efficiency without compromising purity, Nix employs binary caches—also known as substitutes—that store pre-built derivation outputs for direct download. The primary binary cache for NixOS, hosted at cache.nixos.org, is populated by Hydra, the project's continuous integration system, which evaluates Nix expressions from the nixpkgs repository and builds artifacts across supported platforms like x86_64-linux. Hydra signs these artifacts cryptographically using public keys, allowing Nix to verify their integrity and authenticity before substitution.[53][54]
When realizing a store path, Nix first queries configured binary caches for substitutes; if available and verified, it downloads the pre-built binary, bypassing local compilation. In cases of a cache miss—such as for custom or recently updated derivations—Nix falls back to building locally within a sandboxed environment, enforcing isolation via features like network restrictions and filesystem namespaces to maintain purity. This hybrid model supports seamless integration with the Nix store, where downloaded or built outputs populate hashed paths.[53][55]
The use of binary caches significantly mitigates the computational demands of source-based building, which can otherwise take hours for complex packages like web browsers or compilers on standard hardware. With widespread cache coverage in NixOS as of 2025, including optimizations in parallel evaluation and cache distribution, substitution often reduces effective build times to seconds for cached items, enabling faster deployments while preserving reproducibility.[55][56]
Modular System Configuration
The NixOS module system is a declarative framework implemented in the Nix language, designed to enable extensible and composable system configurations by distributing declarations across multiple independent files, referred to as modules. Each module is a Nix expression—typically a function accepting arguments such asconfig, options, pkgs, and lib—that returns an attribute set containing three primary attributes: options, config, and imports. The options attribute uses lib.mkOption to declare hierarchical configuration parameters, specifying details like type, default value, and description; for example, nested options allow structuring complex settings under paths like services.ssh.enable. The config attribute defines actual values for options, often employing conditional logic with lib.mkIf to activate features based on user choices. The imports attribute accepts a list of paths or expressions to other modules, allowing seamless extension and reuse; this hierarchical composition permits users to build tailored configurations by importing base modules and overriding specifics without altering upstream code.[57]
Central to the module system's robustness is its type system and validation mechanism, which enforces correctness during configuration evaluation. Options are assigned explicit types from the lib.types library, such as lib.types.bool for true/false toggles, lib.types.str for text inputs, lib.types.int for integers, or composite types like lib.types.listOf lib.types.str for ordered collections; these types include built-in validation to reject incompatible values, triggering descriptive errors if mismatches occur. Merging definitions from multiple imported modules happens automatically based on the option's type: attribute sets recurse and combine, lists append in declaration order, and single-value options take the last-defined value unless overridden. To resolve conflicts or enforce order, the system provides priority mechanisms via lib.mkForce (highest precedence, overriding all), lib.mkDefault (standard priority of 1000, overridable), lib.mkBefore (appends after but before defaults), and lib.mkAfter (appends last); numerical priorities can be customized with lib.mkOverride, where lower numbers win, ensuring predictable composition even in large, multi-contributor setups.[11][58]
Services like SSH exemplify the module system's practicality, encapsulating related configurations into self-contained units that users can enable declaratively. The built-in OpenSSH module declares options such as services.openssh.enable (type: bool, default: false) to toggle the daemon and services.openssh.ports (type: list of ints, default: [59]) for network access; when enabled, the module's config automatically generates a systemd service unit, configures host keys, and opens firewall ports via networking.firewall.allowedTCPPorts. A user configuration might import this module alongside defaults with imports = [ <nixpkgs/nixos/modules/services/networking/ssh/sshd.nix> ]; and set services.openssh.enable = true;, inheriting all logic while allowing overrides like adding services.openssh.settings.PermitRootLogin = "no"; for security. As of 2025, NixOS incorporates over 1,000 such modules in its core distribution, spanning hardware, networking, and applications, which fosters a vibrant ecosystem for third-party contributions and custom extensions without risking system integrity.[60]