Nix flakes make evaluation inputs explicit
Eelco Dolstra's introduction explains why reproducible builds need a reproducible way to select dependencies and discover project outputs.
Nix can build a carefully described dependency graph while leaving the evaluation that selects that graph dependent on a user's environment. A reference to a package collection through the search path is an input even when it does not appear as an argument to the function being read. Flakes address that earlier stage. They give a project explicit dependencies and a standard interface, with a lock file recording the revisions used for evaluation.
Eelco Dolstra's May 25 introduction explains the motivation and presents the experimental implementation. The original flakes RFC, published the preceding year, provides the design background. The interface is still experimental. Its significance lies in making the dependency-selection problem part of Nix itself, rather than requiring every project to invent its own convention.
I am drawn to declarative systems because they make operational intent available for inspection. That benefit weakens when the same visible expression selects different inputs on different machines. Explicit evaluation dependencies address a problem that occurs before any builder has a chance to reproduce its output.
Building a graph and selecting a graph are different tasks
Dolstra distinguishes reproducible builds from reproducible evaluation. A derivation graph describes the build work and its dependencies. Evaluating Nix expressions determines that graph. If evaluation consults an undeclared environment variable or a user's package configuration, two invocations can select different graphs even when both subsequently build their selected derivations correctly.
The motivation is concrete. Nix expressions can consult arbitrary files, Git repositories, and the Nix search path. A user's ~/.config/nixpkgs/config.nix can affect evaluation outside the project repository. builtins.currentSystem supplies another ambient value. These are inputs with consequences, regardless of whether the expression's functional appearance makes them easy to overlook.
Pinning a build dependency after selection does not solve an ambiguity in how that dependency was selected. A project needs a defined account of which package collection it means and which revision of that collection applies. The problem becomes more visible when the project combines repositories maintained independently. Each repository can bring assumptions that are reasonable in isolation but inconsistent with the caller's environment.
Flakes make dependencies part of the project description. A source tree contains a flake.nix file that declares inputs and describes outputs. Nix resolves those inputs and passes them to the output-producing function. The dependency relationship becomes available to tooling, rather than remaining an implementation detail buried inside arbitrary fetching expressions.
A lock records a resolution without changing the request
A dependency declaration often names a branch because that expresses the intended update policy. A particular evaluation needs an exact revision. Those requirements are compatible when the declaration and its resolved state are stored separately. The flake file can describe which source the project follows, while flake.lock records the version selected for a reproducible evaluation.
The RFC describes generating the lock file automatically and checking a recorded content hash. An exact revision identifies source, while the content hash checks the fetched representation. This is especially relevant to downloaded archives, where fetching a repository snapshot does not inherently provide the same verification process as operating on Git's own objects. The design therefore records more than a convenient branch label.
Committing the lock file makes that resolution available to another user of the project. A dependency update then has an inspectable artifact of its own. The project can distinguish modifying its source from changing the package collection against which that source is evaluated. Both can affect a build, but they represent different maintenance decisions.
This does not make a moving top-level reference immutable. Asking for the latest state of a repository can still select a different top-level project tomorrow. Its lock file explains the dependencies of the selected project revision. Reproducing a particular result also requires identifying that top-level revision, rather than assuming a short repository name denotes one permanent source tree.
Restricted evaluation makes the lock meaningful
A lock file cannot capture an arbitrary dependency that evaluation never declared. The RFC therefore couples dependency locking with restrictions on evaluation. It proposes pure evaluation that denies access to unrelated local files and environmental values. This narrows what can influence the result to the flake's source and permitted dependencies.
The same restriction changes established workflows. A configuration that relies on a user's Nix search path can no longer treat that path as an invisible parameter. A package customization supplied through a home-directory file needs another route into the evaluation. These are compatibility costs of removing ambient dependencies, not incidental inconveniences unrelated to reproducibility.
The RFC includes an impure mode for cases that need the older access patterns. That escape changes the assumptions under which evaluation is performed. A project cannot simultaneously rely on arbitrary external configuration and claim that a lock file alone identifies every relevant input. The distinction should be visible in how its evaluation is invoked and described.
My preference is to make such exceptions explicit. A declaration that documents where external state enters the process is more useful than treating the word functional as proof that the state cannot matter. Flakes move that discussion into a common mechanism, so maintainers need not infer every project's dependency policy from its fetching code.
Standard outputs let tools find the intended artifacts
The other major part of the design is discoverability. Existing repositories use conventions such as default.nix and release.nix, but those conventions do not fully describe what a project provides. A repository may contain packages and a NixOS module without a uniform way for a caller to discover both. Standard flake outputs give those artifacts named roles.
An output is still a Nix value. Certain output names have meanings to tools, which impose corresponding expectations about their values. A package output needs a derivation that Nix can build. Checks describe derivations intended for validation. The distinction allows a project to publish multiple useful results without asking a consumer to understand every internal file before selecting one.
nix build
The original RFC uses this command to build the current flake's default package. Its usefulness comes from the standard interface that makes a default discoverable. The command is meaningful in the experimental flake implementation described by the proposal. It is not evidence that an arbitrary installed stable Nix already provides the entire experimental workflow.
The same design supports NixOS modules as outputs. That makes composition a relationship between projects with declared interfaces. A consumer can obtain a module from a dependency while using the lock to identify the dependency's source. Locating an artifact and selecting its version consequently become parts of one project model, rather than separate conventions each consumer has to reconstruct.
Registries shorten names without replacing dependency identity
The RFC also describes registries mapping symbolic names to direct flake references. These improve command-line use by allowing a short identifier to stand for a repository location. A local registry can override a global entry. That is useful for interactive work, but it also explains why a friendly name alone is insufficient evidence of an exact dependency.
Resolving a name and recording the resolution serve different purposes. The registry helps locate a flake when a reference is resolved. The lock preserves the selected dependency state for the project. Keeping those roles separate avoids treating a user's local alias as a portable specification of the source another machine must use.
Restricted evaluation also gives the RFC a basis for caching evaluated attributes. Its proposed cache identity combines the flake source with the lock file. That follows from the dependency model. Reusing an evaluation result requires knowing that its relevant inputs are unchanged, including the dependencies selected before building begins. The cache is therefore an application of the input restrictions, not an independent substitute for them.
The original proposal also identifies source availability as an unresolved issue. A recorded revision does not ensure a repository or archive will remain available indefinitely. Locking determines what to retrieve; preserving dependencies addresses whether retrieval remains possible. That distinction limits the claim appropriately without reducing the value of knowing the exact intended source.
May's introduction presents flakes as a way to bring these related concerns into Nix's project interface. The implementation is experimental, and particular commands can evolve with it. The enduring technical contribution of the design is specific. A project declares dependencies, records their resolution, and exposes outputs through names that tools can interpret. This provides a clearer basis for reproducing evaluation across machines before the resulting derivations enter the build process.