I have a package declared in a flake.nix file, and since the build process is time-consuming, I cached it earlier in my private binary Nix cache. However, when I attempt to build the package again using nix build, it starts the build process from scratch. The binary cache works since dependencies are being fetched from it.
My question is: Can nix build retrieve the final build result from the cache instead of rebuilding the package from scratch?
Let me clarify my situation better. I have a flake with packageA defined in outputs. I built this package under commit ABC and it’s cached in my private binary cache.
My expectation was that when I go to another machine, clone the repo at the same commit ABC, and run nix build, it should fetch packageA from my cache instead of rebuilding it.
However, what I’m experiencing is that nix build starts rebuilding from scratch, even though the commit hasn’t changed and the package should be available in my cache.
Interestingly, I tested a different approach: I created a separate flake that imports my original flake as an input, pinned to the specific commit ABC where I know the package is cached. In this case, it worked perfectly - Nix fetched the pre-built package from cache and I immediately had my dev shell ready.
This suggests that when building directly from the flake source (nix build in the repo), Nix treats it differently than when the flake is used as a pinned input from another flake.
Is this behavior documented somewhere in the manual? It seems like there’s a distinction between building a flake “locally” versus consuming it as a locked input, and I’d like to understand the caching implications better.
Your expectation is correct. Assuming the architecture is the same, substituters are configured correctly, etc. if nix is not using the cache then it sounds like a nix bug. But without a working example it’s difficult to confirm that.
Flakes aren’t “built”, they are simply a wrapper around normal Nix code, which is evaluated, not built. The “package” defined in the flake is a derivation, which nix build evaluates. After it is evaluated, it will be a file in the Nix store ending in .drv. That file describes the dependencies of your derivation, how to build it and its output paths. These output paths (which don’t exist yet at this point) are calculated by hashing some parts of the .drv file. After the .drv file is ready, nix build will build it and its dependencies (execute the instructions it contains). Once it’s built, the output will be placed at the output path specified in the .drv file: the one calculated with the hash.
When you build again, it will re-create the .drv file during the evaluation of your Nix code. If the output path that the .drv file contains doesn’t change, it means the hash didn’t change, which means the instructions to build the package didn’t change. If that’s the case, then Nix realizes there is pretty much no point in building again if the instructions are the same and just gives you the built path that is already in the Nix store. Binary caches work in a similar way. If it’s there with the same hash, then it can simply be downloaded.
This means that if you are sure that you configured your binary cache correctly, then the .drv file used to build packageA for the binary cache differs from the .drv file your computer is now creating. Without seeing your code, we can’t really try to help you much in figuring out what’s different (since the code is the same as you said). Based on the information you gave, I can give a few reasons this might happen:
You don’t commit your flake.lock. You should commit it in case you don’t. It ensures your inputs are the same and locked to a specific commit.
You use inputs that you haven’t defined that come from the global registry. This can be done in pure evaluation mode even though it technically is an impurity. What it means is doing outputs = { nixpkgs, ... }: for example, but without actually setting inputs.nixpkgs. Nix will then attempt to get nixpkgs from the global registry, which is enabled by default on NixOS. The nixpkgs of the global registry on your computer might differ from the one on the machine you used to build the package for the binary cache.
You are using impure evaluation mode. If you are doing this, then there are many more differences that could accidentally cause the .drv files to be different. Using impure evaluation mode with flakes means using the --impure flag on nix build.
Are you by any chance doing any of the above? If not, then I have no idea, sorry.
And are the architectures actually the same, and is the binary cache configured correctly? You might e.g. have an nginx proxy in front of it which refuses to serve large files or something. It would be hard to verify this is a nix bug without direct access to your setup.