Linux builder on macOS
NixOS guest images are aarch64-linux derivations. A Mac cannot build them natively, so Nix needs a Linux builder. The simplest option is nix-darwin’s nix.linux-builder: a small NixOS VM that nix-darwin runs in the background and registers as a remote builder.
Configuration
omaflake’s image flake ships one: darwinConfigurations.linux-builder in nix/hosts/linux-builder.nix (machine-specific additions go into nix/local/darwin.nix). In a flake of your own, the essential part is:
{ ... }: {
nixpkgs.hostPlatform = "aarch64-darwin";
nix.enable = true;
nix.settings.experimental-features = [ "nix-command" "flakes" ];
nix.linux-builder = {
enable = true;
ephemeral = false; # keep the builder's Nix store → fewer rebuilds
config = {
virtualisation.cores = 6;
virtualisation.darwin-builder.memorySize = 12 * 1024; # MiB
virtualisation.darwin-builder.diskSize = 100 * 1024; # MiB
# Every image build leaves several GB behind. When less than min-free is left during a build, Nix collects
# garbage until max-free is free. The defaults (1 and 3 GiB) are too small for a ~9 GiB disk image (plan B38).
virtualisation.darwin-builder.min-free = 20 * 1024 * 1024 * 1024; # bytes
virtualisation.darwin-builder.max-free = 50 * 1024 * 1024 * 1024;
};
};
}
Adjust cores, memory and disk to your machine. min-free/max-free make the builder clean up after itself: when a build leaves it less than 20 GiB, Nix deletes unused store paths until 50 GiB are free (everything it deletes can be rebuilt or downloaded again). The builder’s own defaults (1 and 3 GiB) are too small: a NixOS disk image alone needs ~9 GiB while it is written.
Activation
The app does all of this by click (Doctor → Nix “Install …”, nix-darwin “Set Up …”), the CLI with omaf builder setup; by hand:
nix-darwin takes over /etc/nix/nix.conf; move the unmanaged one aside first (rename, don’t delete). Do the same for any other file the activation reports as “unexpected” in /etc (e.g. /etc/zshrc, /etc/bashrc):
sudo mv /etc/nix/nix.conf /etc/nix/nix.conf.before-nix-darwin
cd nix
sudo nix --extra-experimental-features 'nix-command flakes' \
run nix-darwin/master#darwin-rebuild -- switch --flake "path:$PWD#linux-builder"
The extra flags are needed because without nix.conf nothing enables flakes. Later activations: sudo darwin-rebuild switch --flake "path:$PWD#linux-builder". Check with sudo ssh linux-builder uname -m (expected: aarch64).
Afterwards, every NixOS image build (omaf build, the app) runs on the Linux builder transparently.
Pitfalls
- Default size is too small. The default builder (1 core, 3 GiB RAM, 20 GiB disk) ran out of space on the first image build. Use the larger
configabove. - Two-stage setup. A changed builder configuration is itself an
aarch64-linuxbuild, so it is built by the builder that is currently running. Activate with the defaults first, then switch to the larger config. - Disk full (“No space left on device”). Happens with the default
min-free/max-free(above). One-off clean-up: the app’s Doctor → Linux builder → “Clean Up …”,omaf builder clean-up, orsudo ssh linux-builder 'nix-store --gc'. Changed builder settings: Doctor → “Apply Settings …” oromaf builder apply(both run thedarwin-rebuildabove). -
Disk is not resized automatically. The builder disk (
nixos.qcow2in the builder’sworkingDirectory) is only created if it is missing. To apply a newdiskSize, move the old disk away and restart the builder; it needs about a minute before it accepts connections:sudo mkdir -p ~/.omaflake/_to_delete sudo mv /var/lib/linux-builder/nixos.qcow2 ~/.omaflake/_to_delete/linux-builder-old.qcow2 sudo launchctl kickstart -k system/org.nixos.linux-builder sudo ssh linux-builder 'nproc; free -g; df -h /'sudo mkdirmakes~/.omaflakeroot-owned if it did not exist; give it back withsudo chown -R "$USER":staff ~/.omaflake. -
/etc/synthetic.confmust exist. Without it the activation fails with astat/chmoderror and/nixis missing after the next reboot. Expected content (check again after macOS updates):printf 'nix\nrun\tprivate/var/run\n' | sudo tee /etc/synthetic.conf sudo chmod 644 /etc/synthetic.conf - Existing Nix installation. An existing multi-user Nix install (
nixbldgroup, GID 350) is adopted by nix-darwin; no reinstall is needed. - No KVM. There is no KVM inside the builder, so image creation runs under TCG emulation. It is slower, but the
minimalexample recipe (including Hyprland) still builds in a few minutes.