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:
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/<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:
# Clone Nixpkgsgit clone https://github.com/NixOS/nixpkgscd nixpkgs
# Evaluate the cowsay derivation with lazy trees disabledtime nix eval \ --no-eval-cache \ .#cowsay
# Make a meaningless change to the source treeecho "" >> flake.nix
# Evaluate the cowsay derivation againtime 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
Now the same sequence of steps with lazy trees:
# Enable lazy treesecho "lazy-trees = true" | sudo tee -a /etc/nix/nix.custom.conf
# Make a meaningless change to the source treeecho "" >> flake.nix
# Evaluate the cowsay derivationtime nix eval \ --no-eval-cache \ .#cowsay
# Make a meaningless change to the source treeecho "" >> flake.nix
# Evaluate the cowsay derivation againtime nix eval \ --no-eval-cache \ .#cowsay
You’ll notice two things:
- The evaluation should be substantially faster with lazy trees (if not, please let us know via email or on Discord).
- 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
# Disable lazy treesecho "lazy-trees = false" | sudo tee -a /etc/nix/nix.custom.conf
# Evaluate the cowsay packagenix eval \ --no-eval-cache \ --store ./tmp \ .#cowsay
# See how much disk space is used by the local Nix storedu -sh ./tmp
On my machine, this yields 304 megabytes. Now let’s try the same thing with lazy trees enabled:
# Delete the local Nix store to start from scratchsudo rm -rf ./tmp
# Enable lazy treesecho "lazy-trees = true" | sudo tee -a /etc/nix/nix.custom.conf
# Evaluate the cowsay packagenix eval \ --no-eval-cache \ --store ./tmp \ .#cowsay
# See how much disk space is used by the local Nix storedu -sh ./tmp
# Delete the local Nix store to clean upsudo 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?
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:
real 0m10.787suser 0m3.655ssys 0m6.759s
Wall time without lazy trees: almost 11 seconds. Disk usage without lazy trees: 433 megabytes.
Evaluation time with lazy trees:
real 0m3.500suser 0m1.762ssys 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:
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:
{ myPackage = myDerivationFunction { src = ./.; };}
This is a much more efficient way:
{ 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:
{ 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:
sudo determinate-nixd upgrade
If you don’t yet have Determinate Nix installed, you can install it on macOS using our graphical installer:

Install Determinate Nix on macOS now
Apple Silicon and Intel
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:
determinate-nixd version
And again, once you are on the most recent version, you can enable lazy trees in your custom Nix configuration:
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:
- Improving evaluation caching
- Bringing parallel evaluation to more Nix operations
- Providing multi-threaded unpacking of flakes, both flakes from tarballs (like those from
FlakeHub ) and those from source forges like GitHub.
Stay tuned!