July 11, 2022·4 min read·engineeringnixstoreslinuxcli

Nix can create a user-owned store without changing logical store paths

Nix 2.10 adds an automatic Linux fallback when a normal store cannot be set up. A diverted physical store and a namespace helper let programs retain their expected logical paths.

Installing Nix without permission to create /nix has a problem beyond choosing another directory for downloads. Programs in a Nix store can expect their dependencies at the store's logical paths. Moving the physical files alone does not make those expectations disappear. Nix 2.10 adds an automatic Linux fallback that creates a chroot store under the user's data directory. The July 11 release notes demonstrate running a downloaded static Nix binary without a preexisting /nix installation.

The default location is ~/.local/share/nix/root. The mechanism preserves a distinction between where the files physically live and the store paths programs see when they run. That distinction explains why this is more than changing an installation prefix.

Store selection checks the existing installation first

The store-opening code in 2.10 first checks whether the ordinary state directory is readable and writable. If it is, Nix uses the local store. Otherwise, an existing daemon socket provides another route. The automatic chroot fallback comes after those choices.

The fallback is Linux-specific and applies to a non-root user when the normal state directory does not exist and no explicit store parameters were supplied. It creates a root directory under the user's data directory, then opens a local store configured with that root. If creating the directory fails, the implementation returns to ordinary local-store handling.

This means the change is not a blanket rule that every non-root invocation receives a separate private store. A working normal store or daemon remains the preferred existing route. The fallback addresses the missing-installation case described by the release notes, with additional conditions visible in the implementation.

I find that behavior useful for declarative tooling because trying the model should not always require changing a machine's global layout first. The important part is retaining an explainable store model after that convenience. A user-owned directory should not imply that packages have silently acquired different dependency paths.

Physical and logical store locations differ

The local-filesystem store configuration defines a root setting that prefixes the store's physical directories. It places state below nix/var/nix and store contents below nix/store within that root. The class also translates a logical store path into its physical location.

For the default fallback root, a logical path under /nix/store therefore has physical storage beneath the user's chroot directory. These are two views of one store arrangement. The logical name remains the one used by the package's references, while the physical name identifies files accessible through the host filesystem.

That separation is necessary for reusing programs expecting the logical store layout. A binary that refers to an interpreter or shared dependency at a store path cannot discover an arbitrary relocated file merely because an installer put it in a home directory. Execution needs an environment where its expected path resolves appropriately. A path printed as part of a package's identity should consequently be interpreted through the store's logical view. Looking for it at the host filesystem root can miss its physical location in a diverted store.

The release notes illustrate the resulting invocation using a static Nix executable in the user's home directory.

~/nix run nixpkgs#hello

This example assumes that executable has already been obtained and that the relevant experimental command interfaces are enabled. In the documented missing-store case, Nix reports the fallback directory before running the selected program. The change concerns automatic store setup, not a new meaning for the selected flake package.

A helper supplies the execution view

The run-command implementation checks whether the store's logical directory differs from its real directory. If they differ, it executes a chroot helper. For non-root users, the source explains that this requires new mount and user namespaces.

There is a specific process constraint behind the helper. The source notes that creating the user namespace in the required way does not work from Nix's multithreaded process. Nix therefore executes a single-threaded helper to perform that work. Separating the helper is part of making the execution environment possible, not merely a formatting choice in the implementation.

This also identifies a real prerequisite. A Linux environment must permit the namespace operations required by that execution path. The release's automatic directory selection cannot override a host policy that prevents those operations. The mechanism needs both suitable storage and a way to present the logical paths to the program.

My preference is to explain those requirements directly when describing a rootless setup. Avoiding a global store installation does not mean eliminating the operating system's role. Here the kernel's namespace facilities provide the view in which the stored program can use its expected paths.

The REPL also aligns with installable selection

Nix 2.10 changes another entry point for working with expressions. Behind the experimental repl-flake feature, nix repl accepts installables in the same style as other commands. The REPL documentation shipped with 2.10 illustrates loading flake outputs as well as files and expressions.

The release notes identify a compatibility consequence for the common nix repl '<nixpkgs>' form. With the new interpretation enabled, a file input should be stated through --file, or imported through an expression. A quoted argument that used to mean a file cannot simply be assumed to retain that meaning under the experimental interface.

nix repl --file '<nixpkgs>'
nix repl --expr 'import <nixpkgs>{}'

These examples describe two explicit ways to obtain the prior imported package-set workflow. They make the input kind visible to both Nix and the person reading the command. Flake installables can instead expose their output attributes in the interactive environment.

The user-store fallback and the REPL work affect different stages. Store selection establishes where Nix keeps and executes results, while REPL input selection establishes which values it evaluates interactively. In the July release, both changes make an entry point more convenient while preserving specific rules that explain the resulting environment.