Using Fly.io as a remote builder for nixpkgs

Offload building of your local nixpkgs to a remote builder on Fly.io

A service I enjoy using, and write about often, is fly.io, as it lets me have compute on demand, without having to worry about provisioning and managing an entire server.

Editor's Note: This was a fun experiment, but you are likely better off using a managed service such as nixbuild.net for anything even resembling a production use case.

One of my latest use cases for it is offloading building various nixpkgs from my low-powered laptop to a remote Fly.io builder. I can give the builder more resources for the period when I need them.

Start by creating the builder machine. The default has 256 MB of RAM, while this example requests 2 GB and four CPUs. Adjust those values for the package you are building. Fly machines can be created from container images, so this setup uses the same nixos/nix image as Docker.

fly launch --name tklk-nixbuilder --region ams -o personal --build-only --image nixos/nix --no-public-ips  --vm-cpus 4 --vm-memory 2048

The --build-only flag keeps Fly from starting the machine before the remaining configuration is in place.

The builder needs additional storage because building a package can pull in more of its dependency graph. Create a volume for the Nix store:

fly volumes create nix_store -a tklk-nixbuilder -s 50 -r ams -y

This example uses a 50 GB volume. Fly bills for the storage regardless of whether the machine is powered on, so choose the size based on the expected store. Set the mount path in fly.toml:

[mounts]
  source="nix_store"
  destination="/data/nix"

Do not mount the empty volume at /nix yet. It would hide the existing Nix store, leaving the machine without the binaries it needs. Mount it at /data/nix first, copy the existing store, and then change the destination to /nix.

Some internal Fly.io processes look for the /bin/sleep binary. NixOS stores binaries under hashed /nix/store paths instead of /bin, so that path does not exist. Either create a symlink or set the command in the configuration. This example sets the command explicitly:

[experimental]
  cmd = ['nix-shell', '-p', 'coreutils', '--run', 'sleep inf']

Deploy the machine after the configuration is in place:

fly deploy -a tklk-nixbuilder

After the machine starts, SSH into it to finish the setup:

fly ssh console -a tklk-nixbuilder

First, copy the contents of the nix store into the mounted volume.

cp -a /nix/. /data/nix/

Back on your host machine, change the destination path of the mounted volume in fly.toml to /nix. Then restart the machine with fly deploy -a tklk-nixbuilder.

Generate an SSH key for the remote connection with fly ssh issue:

fly ssh issue -o personal -u root /home/tklk/.ssh/fly_key

Add the following entry to ~/.ssh/config to avoid repeating the connection options:

Host nixbuilder.fly
	StrictHostKeyChecking no
	UserKnownHostsFile=/dev/null
	Port 2200
	User root
	HostName localhost
	IdentityFile ~/.ssh/fly_key

Editor's Note: This SSH entry disables host key checking for nixbuilder.fly. That avoids prompts for the local proxy endpoint, but it also removes SSH host verification for this entry.

Start a local proxy from port 2200 to port 22 on the remote builder. This avoids configuring a WireGuard VPN connection to Fly.io, which is more involved than this port forward. The trailing & runs the proxy in the background. If the terminal closes, the proxy stops and must be restarted before reconnecting.

fly proxy 2200:22 &

With the proxy running, test the remote builder by adding --store ssh-ng://nixbuilder.fly to a nix build command.

Example:

nix build nixpkgs#mercurial --store ssh-ng://nixbuilder.fly --show-trace

The Fly builder uses x86-64 so if your local machine is aarch64, pass --system x86_64-linux to nix-build so the remote builder can build the package. You can copy the x86-64 output back, but it will not run natively on the Arm machine. For aarch64 builds, nixbuild.net supports aarch64 builders, though not macOS builders as of this writing.

Further reading: