Building NixOS images for aarch64 from a x86 Build Platform
In my homelab I run many different single board computers, among them Odroid M1s. However, there’s various versions of Raspbian, Armbian, and Debian, each of them using some custom tweaks. And that’s a lot of time to maintain them. So I really like the idea of NixOS and read that it can be used to build bootable images for computers. I haven’t fully built intuition for all parts in this so it took me a while to get this working. Here’s my notes.
I’m building from a non-NixOS linux computer on x86_64 for aarch64 linux. This has some implications as the settings for the nix package manager standalone are a bit different from NixOS options.
Cross Compilation
The first issue is that the build system is running on a different architecture than the target system. That can be solved by using cross compilation. This requires adding the extra platform in /etc/nix/nix.conf:
extra-platforms = aarch64-linux
Once that’s set, in our flake.nix we add
nixpkgs.buildPlatform.system = "x86_64-linux";
This “works” but would probably take days to build a whole system, even on a reasonably fast build system.
Setting Up Emulation
There’s a better way though; instead of cross compiling everything, we can utilize the NixOS cache to provide us with already built binaries for the desired architecture. In order to do that we need to set up emulation via qemu; on arch/manjaro this is provided by the qemu-user-static-binfmt package. Once installed, we can verify that aarch64 is supported by checking if /proc/sys/fs/binfmt_misc/qemu-aarch64 exists.
Next we need to tell nix about emulation by adding the following to /etc/nix/nix.conf:
extra-platforms = aarch64-linux
extra-sandbox-paths = /usr/bin/qemu-aarch64-static
Now nix can use the binary cache and won’t have to recompile everything.
Flake for building image
Simple flake.nix I’ve used to verify this:
{
description = "build image for odroid-m1";
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
};
outputs = { self, nixpkgs }: rec {
nixosConfigurations.m1 = nixpkgs.lib.nixosSystem {
modules = [
"${nixpkgs}/nixos/modules/installer/sd-card/sd-image-aarch64.nix"
# nixos-hardware is great, but I couldn't use it because my odroid-m1 was set up with core boot instead of petiteboot. So instead i'm setting all the necessary settings manually.
# nixos-hardware.nixosModules.hardkernel-odroid-m1
./sdimage.nix
{
nixpkgs.hostPlatform.system = "aarch64-linux";
# Uncomment if cross-compiling from x86_64:
#nixpkgs.buildPlatform.system = "x86_64-linux";
}
];
};
images.m1 = nixosConfigurations.m1.config.system.build.sdImage;
};
}
sdimage.nix:
{ config, ... }:
{
imports = [ ./common.nix ];
sdImage = {
compressImage = false;
};
}
common.nix:
{ pkgs, ... }:
{
# uboot specific settings:
boot.loader.grub.enable = false;
boot.loader.generic-extlinux-compatible.enable = true;
boot.loader.generic-extlinux-compatible.configurationLimit = 5;
boot.initrd.availableKernelModules = [
"nvme"
];
boot.kernelParams = [ "console=ttyS2,1500000" ];
hardware.deviceTree.name = "rockchip/rk3568-odroid-m1.dtb";
users.users.nixos = {
isNormalUser = true;
extraGroups = [ "users" "wheel" "networkmanager" "video" ];
packages = with pkgs; [ ];
openssh.authorizedKeys.keys = [
"ssh-ed25519 ..."
];
};
# .. settings
}
The image can be built with nix build .#images.m1 and flashed with
sudo dd if=result/sd-image/nixos-image-sd-card-26.05.20260223.2fc6539-aarch64-linux.img of=/dev/sdb bs=4096 conv=fsync status=progress. The result is a image with everything set up as per common.nix. Pretty neat.