background grid image
Image for post changelog-determinate-nix-352
May 15, 2025 by Luc Perkins

Changelog: introducing lazy trees

Lazy trees have been one of the most hotly requested Nix features for quite some time. They make Nix much more efficient in larger repositories, particularly in massive monorepos. And so we’re excited to announce that lazy trees have landed in Determinate Nix version 3.5.2, based on version 2.28.3 of upstream Nix.

We’re confident that this initial release of lazy trees should produce few issues for users. But for the sake of caution, lazy trees is available solely on an opt-in basis for the time being. To start using it today, first install or upgrade Determinate Nix and enable lazy trees in your custom Nix configuration:

/etc/nix/nix.custom.conf
lazy-trees = true

Our co-founder Eelco Dolstra currently has a pull request open to get lazy trees into upstream Nix and we hope to see that merged soon.

Why lazy trees are important

In a nutshell, lazy trees provide faster and less resource-intensive evaluation in many standard usage scenarios. For evaluations inside of the Nixpkgs repo, for example, we’ve frequently seen reductions in wall time of 3x or more and reductions in disk usage of 20x or more—and occasionally reductions far beyond even this.

With lazy trees enabled, Nix scopes file copying to what the specific expression demands—and nothing more. More specifically, Nix uses a virtual filesystem to gather the necessary file state prior to copying anything to the Nix store, “mounting” lazy source inputs to this virtual filesystem at /nix/store/<random-hash>.

This provides a much more parsimonious mode of operation that should quietly make just about everything in Nix better, from standard package builds to development environments to NixOS deployment and beyond.

If you’d like to see this in action, try evaluating something in Nixpkgs yourself. Install or upgrade Determinate Nix and run this sequence of commands:

Evaluate a derivation in Nixpkgs with lazy trees disabled
# Clone Nixpkgs
git clone https://github.com/NixOS/nixpkgs
cd nixpkgs
# Evaluate the cowsay derivation with lazy trees disabled
time nix eval \
--no-eval-cache \
.#cowsay
# Make a meaningless change to the source tree
echo "" >> flake.nix
# Evaluate the cowsay derivation again
time nix eval \
--no-eval-cache \
.#cowsay

Adding an empty line to flake.nix (a meaningless change) invalidates the evaluation cache and forces Nix to copy the entire source tree to the Nix store, otherwise the second evaluation would be faster.

Now the same sequence of steps with lazy trees:

Evaluate a derivation in Nixpkgs with lazy trees enabled
# Enable lazy trees
echo "lazy-trees = true" | sudo tee -a /etc/nix/nix.custom.conf
# Make a meaningless change to the source tree
echo "" >> flake.nix
# Evaluate the cowsay derivation
time nix eval \
--no-eval-cache \
.#cowsay
# Make a meaningless change to the source tree
echo "" >> flake.nix
# Evaluate the cowsay derivation again
time nix eval \
--no-eval-cache \
.#cowsay

You’ll notice two things:

  1. The evaluation should be substantially faster with lazy trees (if not, please let us know via email or on Discord).
  2. With lazy trees, you won’t see any indication that Nix is copying the source tree to the Nix store. That’s because it’s able to determine, via evaluation, that only the sources for the cowsay package are necessary to achieve its ends.

Saving time is good. Also good: using less disk space. I’ll provide an example.

First, let’s evaluate something in Nixpkgs using a ./tmp subdirectory inside the current directory as a fresh Nix store:

Disk space usage with lazy trees disabled
# Disable lazy trees
echo "lazy-trees = false" | sudo tee -a /etc/nix/nix.custom.conf
# Evaluate the cowsay package
nix eval \
--no-eval-cache \
--store ./tmp \
.#cowsay
# See how much disk space is used by the local Nix store
du -sh ./tmp

On my machine, this yields 304 megabytes. Now let’s try the same thing with lazy trees enabled:

Disk space usage with lazy trees enabled
# Delete the local Nix store to start from scratch
sudo rm -rf ./tmp
# Enable lazy trees
echo "lazy-trees = true" | sudo tee -a /etc/nix/nix.custom.conf
# Evaluate the cowsay package
nix eval \
--no-eval-cache \
--store ./tmp \
.#cowsay
# See how much disk space is used by the local Nix store
du -sh ./tmp
# Delete the local Nix store to clean up
sudo rm -rf ./tmp

This yields 13 megabytes. That’s over 23 times less disk space. This specific number isn’t generalizable across all situations, of course, but it does illustrate just how much more efficient Nix can become in a standard scenario when using lazy trees.

Would you like access to private flakes and FlakeHub Cache?

Sign up for FlakeHub

CI comparisons

To illustrate some of these differences in a CI environment, I’ve set up a GitHub repo at DeterminateSystems/lazy-trees-comparisons. The workflow configuration shows the sequences of steps involved (similar to the steps above).

As an example, check out this CI run, which evaluates the stdenv attribute of Nixpkgs on GitHub Actions’ ubuntu-latest runner. The evaluation time without lazy trees:

Evaluation time without lazy trees
real 0m10.787s
user 0m3.655s
sys 0m6.759s

Wall time without lazy trees: almost 11 seconds. Disk usage without lazy trees: 433 megabytes.

Evaluation time with lazy trees:

Evaluation time with lazy trees
real 0m3.500s
user 0m1.762s
sys 0m1.701s

Wall time without lazy trees: 3.5 seconds. Disk usage with lazy trees: 11 megabytes.

Again, a substantial difference. This is, of course, just one result of one run, but it’s consistent with more general runs and we encourage you to make your own comparisons.

New warning

When using Determinate Nix with lazy trees, you may see warnings like this:

Inefficient copy warning
warning: Performing inefficient double copy of path '«github:nixos/nixpkgs/0c0bf9c057382d5f6f63d54fd61f1abd5e1c2f63»/' to the store. This can typically be avoided by rewriting an attribute like `src = ./.` to `src = builtins.path { path = ./.; name = "source"; }`.

You’ll see this in cases where you specify the root of a repo as the source for a derivation. When you use ./. as the source for a derivation, Nix copies your repo to the Nix store under the name /nix/store/<random-hash>-source, which is then copied again as /nix/store/<random-hash>-<other-random-hash>-source.

Because of that, this is an inefficient way to specify your source:

flake.nix
{
myPackage = myDerivationFunction {
src = ./.;
};
}

This is a much more efficient way:

flake.nix
{
myPackage = myDerivationFunction {
src = builtins.path { path = ./.; name = "my-package-source"; };
};
}

The name field can be whatever you like. In a flake, you can also use self instead of ./., as in:

flake.nix
{
myPackage = myDerivationFunction {
src = self;
};
}

Ideally, this wouldn’t be necessary and using ./. wouldn’t produce any inefficiency. But unfortunately, changing this behavior would constitute a backwards-incompatible change that we are highly averse to introducing.

How to upgrade or install

If you already have Determinate Nix installed, you can upgrade to 3.5.2 with one Determinate Nixd command:

Upgrade command for version 3.5.2
sudo determinate-nixd upgrade

If you don’t yet have Determinate Nix installed, you can install it on macOS using our graphical installer:

Logo for graphical installer

Install Determinate Nix on macOS now

Apple Silicon and Intel

On Linux:

Install Determinate Nix on Linux
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | \
sh -s -- install --determinate

On NixOS, we recommend using our dedicated NixOS module.

To verify that you’re on the most recent version of Determinate Nix at any time:

Check Determinate Nix version
determinate-nixd version

And again, once you are on the most recent version, you can enable lazy trees in your custom Nix configuration:

/etc/nix/nix.custom.conf
lazy-trees = true

More to come

Determinate Nix has introduced a number of improvements to Nix in recent months, including JSON logging and automatic fixes for hash mismatches, but we’re confident that the lazy trees release presents the most consequential change to date because it establishes a new, qualitatively improved baseline for Nix performance.

But we are far from finished with improving Nix’s performance. In the coming months, we intend to ship more improvements to Nix’s evaluation performance, including:

  1. Improving evaluation caching
  2. Bringing parallel evaluation to more Nix operations
  3. Providing multi-threaded unpacking of flakes, both flakes from tarballs (like those from FlakeHub) and those from source forges like GitHub.

Stay tuned!


Share
Avatar for Luc Perkins
Written by Luc Perkins

Luc is a technical writer, software engineer, and Nix advocate who's always on the lookout for qualitatively better ways of building software. He originally hails from the Pacific Northwest but has recently taken to living abroad.

Would you like access to private flakes and FlakeHub Cache?

Sign up for FlakeHub