One major issue we’ve had in the Android world with reproducible builds (useful for having f-droid ship APKs signed with the developers key) was that sometimes, filesystems behave non-deterministic.
Iterating over files will return them in a different order depending on where and how you run the same program, which is quite an issue, as Android’s official build tools produce a resource bundle which lists the resources in the order the filesystem returned them, so the resource bundle will be different on every system (but on each system, it will be consistent with each run).
It’s been fixed now (see https://issuetracker.google.com/issues/110237303?pli=1), but it’s a good example of issues people might not even think about regarding reproducible builds.
A different example, mentioned in the blogpost, of using _TIME_ to have the build time in the build (e.g. for an about screen) can luckily be nicely avoided (and still provide the same functionality) by using the timestamp of the current git commit instead.
In general I can recommend https://reproducible-builds.org/ for information about and common issues with reproducible builds :)
Nix has tools for helping with hermetic builds but also allows a single-user install without sandboxing which allows you to do anything your user can do. A lot of what makes Nix work is good defaults and tooling.
The standard environment by default does a bunch of fixing of outputs, like resetting times in tarballs.
nix-build --option build-repeat 5 --check will rebuild many times, checking that they created identical results.
nix-build --option build-repeat 5 --check
nix-build --option sandbox true will only make certain filesystem paths visible, networking and inter-process communication will be blocked. This used to be slow which is why it is disabled by default. It’s now not very slow in recent Linux kernel versions.
nix-build --option sandbox true
My experience with Bazel is that it similarly relies a lot on convention and defaults.
Hermeticity is not just good for reproducibility, but also to ensure that the list of inputs is complete.