Nix flakes explained: what they solve, why they matter, and the future

4 min read Original article ↗

I’ve read the blog post and, at the risk of being shouted at for pointing out things, I am questioning some of it, and have questions about it.

The main problem I can see is the consistent use of the word reproducible (and reproducibility). The commonly shared definition of it would be the one from the Reproducible Builds project, which states

A build is reproducible if given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts.

While that is not universally agreed upon to be “the” definition, I think it’s fair to claim it de facto means bit-for-bit reproducibility of the outputs. Especially in the context of build tooling and package management.

In this article, only one of the eight use of reproducible (and reproducibility) mean that. Well, another one could mean that, the one that links to the zero-to-nix page on the topic, which brings me to the next concern.

Every flake also comes with a flake.lock file that records input revisions. That means that the entire dependency graph becomes deterministic. […]
This makes Nix reproducible in a way that you can’t opt out of even if you want to.

The linked page’s lede states:

[Reproducibility] The ability to consistently and repeatedly produce the same build outputs from the same build inputs.

The linked reference makes the same conflation between a repeatable build environment, and a reproducible build. So I’m not too surprised to see that in this article.

So I don’t think there’s any benefit of the doubt to be had here: either this is meant to be misleading people into believing Flakes are making Reproducible Builds (capitalized noun), or the authors don’t internalize the distinction…

Though given the article (but not the linked reference) states that “builds […] can’t be guaranteed reproducible”, I’m not sure both options are equally likely. Especially with the way the article introduces the problem that there is “no guarantee of reproducibility in […] dependencies”.

While that likely really means something like no guarantee of hermetic/deterministic inputs, the wording inevitably sounds like bit for bit reproducible inputs.

Why am I bringing this up? Because I believe it is crucial that major actors in the NixOS ecosystem consistently and diligently stay transparent with the claims of what Nix can do.

Nix is one level removed from the problem reproducible bits-for-bits builds. Nix instead is what enables a consistent and repeatable build environment that enables bit-for-bit reproducible builds to be achieved.

Getting to these environments (if I’m allowed to simplify) is achieved by making build sandboxes, for every build, and those are made from the instantiated derivations (.drv). The .drv are the mechanism that, when the environment and project would be, produces a bit-for-bit reproducible build.

Any method to get to a given instantiated .drv gives you the same repeatability claims that Nix does. What “Flakes” does, though, is use some special-cased codepaths in Nix to forbid evaluation-time inputs from being left “unpinned”. If there had been a construct in the language or tooling to ensure any “reference” was “pinned”, the same guarantees would apply. Though general improvements on some areas of Nix have been blocked in a stalemate for a while

Nix builds are not inherently reproducible. Though Nix build environments can be. The Nix Expression Language is how you traditionally compute those environments. The Nix Expression Language and its evaluation can use “reproducible builds” concepts to produce a repeatable build environment i.e. producing the same .drv. And just as with Reproducible Builds, this can be achieved when the same inputs are either specified or accounted for.


Now, for an actual more precise question, rather than questioning:

I’m wondering what was meant with this passage:

On top of this, Nix workflows were tied to file naming conventions—the nix-build command tied to default.nix, the nix-shell command tied to shell.nix, and so on—with poor discoverability compared to a lot of modern developer tooling.

This is comparing this to “a lot of modern developer tooling”, but I don’t know what is meant here. Which, “modern developer tooling” or which pattern in those is this about?

Many tooling I know of, modern and older, use different entrypoint files to do different actions. And some use the same entrypoint file to do different actions. Neither are inherently more or less discoverable.

Though having one file per entrypoint to me feels like it is more discoverable, as ls shows me that the project can use nix-shell… while seeing a flake.nix gives me no information about its capabilities.

Can you expand on what this means, and how Flakes solve that? I don’t think the article addresses this, though to be fair I could have missed that.