P2P Nix builds!
Run drv-thru on a NixOS builder, create one time redeemable build tickets or set up persistent access. Anyone with a ticket can build on your machine without getting an account there. Missing inputs upload from their Nix store; finished outputs come back as signed Nix store paths.
You need one NixOS builder and one NixOS client. For ticket builds on a multi-user Nix client, the client must trust the builder's signing key through the drv-thru helper.
Add the module on the machine that should run builds:
{ inputs, ... }:
{
imports = [ inputs.drv-thru.nixosModules.default ];
services.drv-thru.server.enable = true;
}Rebuild, then print the builder's signing key:
sudo nixos-rebuild switch
sudo cat /var/lib/drv-thru/signing-public.keySave that key. The client needs it in the next step.
Create a ticket on the builder:
drv-thru ticket createSend the printed drvthru... ticket to the client.
Add the client module and allow the builder signing key:
{ inputs, ... }:
{
imports = [ inputs.drv-thru.nixosModules.default ];
services.drv-thru.client = {
enable = true;
ticketHelper.trustedBuilderPublicKeys = [
"drv-thru:builder-signing-public-key"
];
};
}Rebuild the client:
sudo nixos-rebuild switchdrv-thru build nixpkgs#hello --ticket "drvthru..."That evaluates locally, uploads missing inputs to the builder, runs the build there, then imports the signed output back into the client's Nix store.
Use this when one client should keep using the same builder without tickets.
On the client, print its Iroh key:
drv-thru key showOn the builder, allow that client:
services.drv-thru.server.trustedClients.alex = {
publicKey = "client-iroh-endpoint-id";
maxBuildTime = "30m";
maxUploadBytes = "20G";
};On the client, trust the builder's signing key:
services.drv-thru.client = {
enable = true;
trustedBuilders.leviathan.publicKey = "drv-thru:builder-signing-public-key";
};Then save the builder address for your user:
mkdir -p ~/.config/drv-thru
cat > ~/.config/drv-thru/builders.json <<'EOF'
{
"builders": {
"leviathan": {
"endpoint_id": "builder-iroh-endpoint-id",
"relay_url": null
}
}
}
EOFOn the builder, drv-thru status prints the server endpoint id. Then build without a ticket:
drv-thru build nixpkgs#hello --builder leviathanYou can still pass the endpoint directly:
drv-thru build nixpkgs#hello --server "builder-iroh-endpoint-id"nix run github:adeci/drv-thru#drv-thru -- build nixpkgs#hello --ticket "drvthru..."This only runs the CLI. It does not install the local helper or change Nix trust settings.
Create a ticket with custom limits:
drv-thru ticket create \
--name friend \
--expires 2h \
--uses 1 \
--max-build-time 30m \
--max-upload-bytes 20GBind a ticket to one client endpoint:
drv-thru ticket create --bind-client "client-iroh-endpoint-id"Inspect and manage tickets on the builder:
drv-thru ticket inspect "drvthru..."
drv-thru ticket list
drv-thru ticket reveal "ticket-id"
drv-thru ticket revoke "ticket-id"Defaults: tickets expire after 2 hours, allow one use, allow 30 minutes of build time, and allow 20 GiB of input upload. list does not print bearer secrets; reveal does.
NIXPKGS_ALLOW_UNSUPPORTED_SYSTEM=1 drv-thru build . --impure --ticket "drvthru..."
drv-thru build . --refresh --ticket "drvthru..."
drv-thru build . --override-input nixpkgs github:NixOS/nixpkgs/nixos-unstable --ticket "drvthru..."
drv-thru build . --rebuild --ticket "drvthru..."
drv-thru build nixpkgs#hello --ticket "drvthru..." --no-nom--impure, --refresh, and --override-input affect local evaluation before inputs upload. --rebuild asks the remote builder to rebuild and check the derivation. --no-nom uses plain logs.
If the requested outputs are already valid in the local Nix store, drv-thru skips the remote builder and prints the local output paths.
drv-thru status
drv-thru status --watchRun these on the builder to see active builds and the waiting queue.
There are two separate trust decisions:
- who may spend CPU and disk on the builder
- whose build outputs may enter the client's Nix store
Tickets and server.trustedClients.* control builder access.
A builder endpoint id is an address, not an auth secret. Keep it local if you do not want people probing reachability, but access still requires a trusted client key or a ticket. Named builders can live in /etc/drv-thru/builders.json from the NixOS module or in ~/.config/drv-thru/builders.json for one user; the user file overrides the system file.
Nix trusted users, client.builders, client.trustedBuilders, and ticketHelper.trustedBuilderPublicKeys control output import trust.
A ticket alone is not enough for an untrusted multi-user Nix client to import outputs from a new builder. One of these must also be true:
- the local user is a trusted Nix user
- the builder signing key is in
nix.settings.trusted-public-keys - the user can access the drv-thru import helper socket and the builder key is in the helper allowlist
The helper is a local root service. It only accepts allowlisted builder keys, loopback HTTP cache URLs, and exact /nix/store/... paths. It does not run arbitrary commands or arbitrary Nix options.
Outputs move back through Nix's signed binary cache path:
- the builder signs
.narinfofiles - the client mirrors the needed cache files over Iroh
- local Nix imports the paths after checking signatures
The server keeps a persistent signed cache under /var/lib/drv-thru/cache. First request for a large uncached closure may spend time filling cache entries; later builds reuse them.
Raw nix-store --export / nix-store --import is still used for server-side input upload. It is not used for client output import.
services.drv-thru = {
package = inputs.drv-thru.packages.${pkgs.stdenv.hostPlatform.system}.default;
server = {
enable = true;
dataDir = "/var/lib/drv-thru";
secretKeyFile = "/var/lib/drv-thru/secret.key";
maxConcurrentBuilds = 1;
outputCacheMaxParallelFills = null; # auto
recentBuildsLimit = 20;
trustedClients.alex = {
publicKey = "client-iroh-endpoint-id";
maxBuildTime = "30m";
maxUploadBytes = "20G";
};
};
client = {
enable = true;
narFetches = null; # auto
builders.leviathan = {
endpointId = null;
endpointIdFile = "/run/secrets/drv-thru-leviathan-endpoint-id";
relayUrl = null;
publicKey = "drv-thru:builder-signing-public-key";
};
trustedBuilders.other.publicKey = "drv-thru:other-builder-signing-public-key";
ticketHelper = {
enable = true; # defaults to client.enable
group = "wheel";
trustedBuilderPublicKeys = [
"drv-thru:ticket-only-builder-signing-public-key"
];
};
};
};Module state lives in /var/lib/drv-thru. Wheel users can create and inspect tickets. The server Iroh secret key and signing secret key stay private to the drv-thru service user.

