Protocol

Standardized Interface

One goal is to make the build protocol a standardized interface, allowing to make the sandboxing mechanism used by the build process pluggable.

Nix is currently using a hard-coded libseccompexternal link based sandboxing mechanism and another one based on sandboxdexternal link on macOS. These are only separated by compiler preprocessor macrosexternal link within the same source files despite having very little in common with each other.

In Snix, the Builders need to implement a trait, and there are multiple implementations. In addition to an OCIexternal link builder1, we also include a gRPC client (and server adapter), allowing to run the builder both locally or remotely, or plug in your entirely separate Builder, as long as it speaks the same gRPC protocol.

Check build/protos/build.proto for a detailed description of the protocol, individual fields, and the tests in glue/src/tvix_build.rs for some examples.

While we’re somewhat confident about the BuildRequest, the RPC method itself will change to a stream of events, so we can stream logs/build telemetry to the requesting client.

Unaware of Nix sandbox internals

The environment in which builds currently happen is currently very Nix-specific. In Snix, we don’t want to maintain all the intricacies of a Nix-specific sandboxing environment in every builder, and instead only provide a more generic interface, receiving more generic build requests (and translate Derivations into this format). 2

Another goal of the builder protocol is to not be too tied to the Nix implementation itself, allowing it to be used for other builds/workloads in the future (and experimenting with different hashing schemes etc without having to change builder code).

In concrete terms, this means the builder protocol is versatile enough to express the environment a Nix build sets up, while it itself is not aware of “what any of this means”.

For example, it is not aware of how certain environment variables are set in a nix build, but provides the necessary infrastructure to specify environment variables that should be set.

It’s also not aware of what nix store paths are. Instead, it allows:

  • specifying a list of paths expected to be produced during the build
  • specifying a list of castore root nodes to be present in a specified inputs_dir.
  • specifying which paths are write-able during build.

In case all specified paths are produced, and the command specified in command_args succeeds, the build is considered to be successful.

This happens to be sufficient to also express how Nix builds works.

More hermetic builds, Build Provenance

Nix uses derivations (encoded in ATerm) as nodes in its build graph, but it refers to other store paths used in that build by these store paths only. As mentioned before, store paths only address the inputs - and not the content.

This poses a big problem in Nix as soon as builds are scheduled on remote builders: There is no guarantee that files at the same store path on the remote builder actually have the same contents as on the machine orchestrating the build. If a package is not binary reproducible, this can lead to so-called frankenbuildsexternal link .

This also introduces a dependency on the state that’s present on the remote builder machine: Whatever is in its store and matches the paths will be used, even if it was maliciously placed there.

To eliminate this hermiticity problem and increase the integrity of builds, we’ve decided to use content-addressing in the builder protocol.

In the long run, recording this information is gonna improve our posture regarding Build Provenanceexternal link .


  1. With a well-defined builder abstraction, it’s also easy to imagine other backends such as a Kubernetes-based one in the future. ↩︎

  2. There have already been some discussions in the Nix community, to switch to REAPI: https://discourse.nixos.org/t/a-proposal-for-replacing-the-nix-worker-protocol/20926/22 ↩︎