February 22, 2018·6 min read·engineeringnixclistoresreproducibility

Nix 2.0 unifies commands around stores

Nix 2.0 introduces an experimental command interface backed by a broader store abstraction, with new transfer, trust, and evaluation behaviour.

Copying a package to a remote machine and publishing it to a binary cache are related operations. Both transfer store paths, but older Nix commands expose them through different interfaces. The Nix 2.0 release notes, dated February 22, describe a broader store abstraction that brings these operations together. The experimental nix command makes that change visible through subcommands such as copy and path-info.

The new command is intended eventually to replace the separate nix-* programs, but the release explicitly says it replaces only some of their functionality. Its interface remains subject to change. This is a substantial release with a partial new interface, so treating it as a completed removal of the older commands would misdescribe the transition.

I am interested in declarative infrastructure because it makes desired state explicit. Nix 2.0 improves the operations needed to inspect and move the results of that description. A consistent way to address stores matters when the result can be available locally, through a daemon, or in a remote cache.

Store operations acquire common destinations

Previously, substituters included external programs such as download-from-binary-cache. In 2.0 they become subclasses of the abstract Store class. Commands can consequently ask similar questions of different store implementations. nix path-info can inspect local paths, while the same command with an HTTP cache supplied through --store can inspect that cache.

Store identifiers use a URI-like syntax. An HTTP or HTTPS address denotes a binary cache. A file:// address denotes a local filesystem binary cache. S3 support is available when enabled at compile time. SSH has both a legacy store implementation and an ssh-ng:// implementation using the daemon protocol for broader remote operations.

That model generalizes operations that previously looked unrelated at the command line. A closure transfer, cache publication, and substitution can all be described as copying paths between stores. The implementation still has to account for the capabilities of each destination. HTTP uploading, for example, requires a server that supports PUT requests.

The copy command source at the release tag exposes source and destination store options and opens each through the common store interface. It rejects a request without either --from or --to. When just one side is specified, the other side uses the default store. Its examples include transferring an entire NixOS system closure.

nix copy --to ssh://server /run/current-system

This is an example of the interface supplied by the release, not a request to copy a system now. The command expresses the destination and the selected closure in one invocation. It replaces a workflow-specific transfer spelling with an operation defined over stores.

Package arguments become more consistent

The release notes show nix copy accepting a Nixpkgs attribute, a store path, or a Nix expression. The earlier nix-copy-closure accepted only store paths. A common package argument model therefore changes how much preliminary evaluation a user needs to perform before invoking the transfer.

That consistency also applies to discovery and inspection. nix search searches attribute names, package names, and descriptions, with a cache for later searches. nix eval supplies an alternative to nix-instantiate --eval. The repl subcommand brings the previously external interactive evaluator into the command interface. These commands address different stages of working with expressions and their results.

Help becomes part of that interface. Global help describes available arguments, while subcommand help includes examples. Configuration options from nix.conf become flags, so an option such as the HTTP connection limit can be supplied directly. Many subcommands can emit JSON, allowing their results to be consumed without parsing the ordinary display intended for people.

The progress display also changes. It normally shows a compact account of pending builds and downloads, together with recent builder output. On failure, it displays the last output lines, while nix log can retrieve the full log. If a log is unavailable locally, that command can try configured substituters. The cache abstraction therefore carries diagnostic material as well as package contents.

The run command has its 2018 meaning

In this release, nix run executes a command with the requested packages available. The notes describe it as roughly replacing nix-shell -p, with -c supplying the command directly rather than through a shell. Its documented form includes nixpkgs.hello as the package argument. These details are part of the experimental interface shipped today.

The release pairs this operation with chroot stores. A Linux user unable to install into /nix/store can keep the physical store elsewhere while retaining the logical path expected by cached binaries. The documented example places the physical store below the user home directory and runs the command in a mount namespace that presents it at /nix/store.

That separation addresses a concrete compatibility constraint. A downloaded binary can expect the conventional logical store path even when the user cannot create the physical store there. Relocating the directory without providing that logical view would not express the same arrangement. The store implementation and execution environment cooperate to supply it.

I regard that as a useful operational improvement. Declarative package descriptions are easier to use when a restricted local installation does not automatically require abandoning the available binary substitutes. The release makes the physical and logical locations separate, while documenting the Linux mechanism that supplies the latter.

Transfer retains a trust policy

Unifying store access does not make every store equally trusted. Nix 2.0 records signatures for local paths and propagates them during copying. Configured secret keys can sign locally built paths. A locally built path also receives an ultimately trusted marker, but that marker does not propagate to another store.

That last distinction prevents a transfer from carrying the source machine local trust decision as if it were universal. Signatures can travel as evidence for a destination to evaluate under its own trusted keys. The local marker instead records a fact about the machine where the build occurred. The release treats those as different reasons to accept a path.

Substitutions now require signatures by default, a policy already used on NixOS. Content-addressed paths have a separate exception because their content can establish the relevant identity. nix verify checks whether paths are unchanged and whether they satisfy the documented trust conditions. The common store interface therefore comes with explicit acceptance rules.

Evaluation reproducibility remains a separate problem

Nix 2.0 also introduces pure evaluation mode. The notes describe restrictions on operations such as reading environment variables and fetching data without a content or commit hash. A Git fetch requires a revision. This aims to prevent the same command-line arguments from evaluating differently because an undeclared external input changed.

The distinction from binary reproducibility matters. A build description can be selected reproducibly while its builder still emits different bytes on separate runs. The release supplies additional facilities for that second problem, including a diff hook for differing build rounds and an option to avoid treating such differences as immediately fatal. They address output comparison, while pure evaluation addresses selection of inputs and expressions.

Structured derivation attributes improve another boundary. With __structuredAttrs enabled, Nix supplies attributes to the builder in .attrs.json, preserving values such as nested lists and attribute sets. A companion .attrs.sh supports values representable in Bash. The builder no longer has to recover every structured value from a flattened process environment.

Removal follows the implementation change

Some older mechanisms disappear in this release. Manifest-based substitution and its associated commands are removed after years of replacement by binary caches. nix-push is removed in favour of nix copy. Nix also stops depending on Perl, with components rewritten in C++ or removed and Perl bindings moved into the separate nix-perl package.

Configuration naming reflects the broader model. binary-caches becomes substituters, because a substitute can come from a general store URI. Old configuration names remain supported for compatibility. That is a useful distinction from removed commands, where callers need an actual replacement operation.

Nix 2.0 consequently changes both the implementation and the language used to operate it. Stores become common participants in inspection and transfer, with package arguments and configuration flags made more consistent. The experimental status still limits reliance on the exact new interface, while the tagged examples describe commands and guarantees that are available in this release.

For dependency inspection, nix why-depends shows a chain of references that places one path in another closure. It can identify an unwanted development output retained by an application, giving the operator a concrete explanation of closure size.