U-booting with Nix (prebuilt U-boot images from NixOS Hydra, building custom builds yourself with Nix)

2026/09/26

Don’t worry, I’m not gonna try to convince you to use Nix(OS). The most useful information in this article does not involve installing Nix, it’s just not super well known so I’d like to call some attention to it. It’s a bit of a poorly documented mess, as are most things involving Nix. However, if the nixpkgs u-boot configurations do not work for your needs, you will need to dive into a little bit of Nix, if you want to follow instructions later in this article. I imagine a shell into a Docker container with Nix installed will be fine, if you do not want to sully your base system with Nix.

Prebuilt U-boot images

As the title says, I would partly like to call some attention to the fact that NixOS’s Hydra build system makes prebuilt versions of U-boot’s upstream defconfigs. These should be essentially equivalent to pulling u-boot’s repository and building one of the configs, at the very least, it has always worked for me regardless of which distribution I am booting. I’m mainly focusing on Rockchip here as that covers the vast majority of systems that I personally have, but they should be adaptable to various platforms - defer to U-boot’s platform-specific readme files for directions.

You can see what is explicitly supported and built here. You are looking for the entries that have uboot[BOARDNAME], or you can search for the defconfig you are interested in. If you are able to find it within this file, then you are able to download a pre-built u-boot binary from NixOS Hydra. Unfortunately, it’s a bit of a pain to find the specific webpage for downloading inside of Hydra’s web interface, the best way that I can find to navigate to these within Hydra’s web UI is to search a whole jobset, which I’m not going to link directly out of fear of slamming their servers with the modern-age’s rock stupid scrapers crawling their way onto a CPU intensive page. There is unfortunately no other way I can figure out to display all of the U-boot builds together in one place in the web interface, so these directions are a bit ad-hoc - sorry.

I would go with copy pasting this URL directly and inserting Nixpkgs’ name for the configuration into the url, following this scheme: https://hydra.nixos.org/job/nixpkgs/staging-next/uboot[BOARDNAME].aarch64-linux/latest, which will redirect you to the page containing the download link for the latest Hydra build of the config of your choice. So, for example, the U-boot build for 64 bit Raspberry Pi boards would be at https://hydra.nixos.org/job/nixpkgs/staging-next/ubootRaspberryPiAarch64.aarch64-linux/latest, and the download link is ‘u-boot.bin’. If you are looking at a configuration that can be installed to SPI flash, there will be multiple files that are hopefully fairly self-explanatory - for example, Rockchip boards may have a u-boot-rockchip.bin and u-boot-rockchip-spi.bin - the former is for installing to a reserved/unformatted partition in the first ~16MB of eMMC/SD/USB storage, the latter is for installing to SPI flash with something like flashprog or flashrom.

Modifying builds

Now, it’s time for some disgusting copy-pastable nix-build commands, for those scenarios where one wants or needs to override some aspects of nixpkgs’ configuration.

Some of the nixpkgs build configurations do not output some files that are built for the configuration in ‘upstream’ U-boot and are useful, such as images formatted to be installed on SPI flash. A practical example of this is the PINE64 ROCK64 board that I have - it has SPI flash that came fully empty, and I wanted to use that for the bootloader instead of a fragile partition on the same storage as the OS. This is built by U-boot, but is not included in the final ‘result’ nix derivation link, because filesToInstall does not contain u-boot-rockchip-spi.bin. This is an easy fix which shows off the basic manner in which one can use overrides to modify builds. Now, behold with horror:

nix-build -E "
with import <nixpkgs> { };
pkgsCross.aarch64-multiplatform.ubootRock64.override {
  filesToInstall = [ \"u-boot.itb\" \"idbloader.img\" \"u-boot-rockchip.bin\" \"u-boot-rockchip-spi.bin\" ];
}"

If your board is supported upstream by U-boot but is not supported and built inside of nixpkgs, you can build an image for your board this way, and use an override to substitute the relevant defconfig into a build configuration for a board with the same SoC. Another practical example: I recently got a “Gameforce Ace”, a RK3588 handheld by a company that did not exist for very long but eventually shipped these handhelds. Ameridroid had a stock of them remaining, and I seem to have bought the last one. This device interested me because it actually has pretty robust “mainline” Linux support, and there is a defconfig for it inside of U-boot. (Shout out to macromorgan for doing the work getting this merged into Linux and U-boot! It’s much appreciated.) It doesn’t have a build output defined using that defconfig inside of nixpkgs, because of its obscurity. There are some other RK3588 boards that are supported inside of nixpkgs, however, such as Xunlong’s Orange Pi 5 series and Radxa’s ROCK 5 series. For our purposes, the Rock 5C build configuration is the closest, so we will use that.

nix-build -E "
with import <nixpkgs> { };\
pkgsCross.aarch64-multiplatform.ubootRock5ModelC.override {
  defconfig = \"gameforce-ace-rk3588s_defconfig\";
}"

For thoroughness: you can apply both of these at once - for example, the Rock 5C build configuration is not in the NixOS 26.05 channel at this time, so I instead used an Orange Pi 5 board instead, which necessitated adjusting the output files to exclude the SPI flash output file for Reasons:

nix-build -E "
with import <nixpkgs> { };
pkgsCross.aarch64-multiplatform.ubootOrangePi5.override {  
  defconfig = \"gameforce-ace-rk3588s_defconfig\";
  filesToInstall = [ \"u-boot.itb\" \"idbloader.img\" \"u-boot-rockchip.bin\" ];
}"

(Flashing gets a little more complex here, because the device is already pre-partitioned for Android and you can decide to either preserve those partitions by following more manual instructions on how to flash the individual idbloader.img ‘preloader’ and the u-boot.itb U-boot images at the correct offsets to not disturb data that is interleaved in the unused space between the preloader and U-boot, or just treat it like any old Linux device and nuke Android. It also ships with U-boot, but an older version of it that doesn’t seem to be able to UEFI boot, which is desirable for me. I have some use for Android, so I’d like to figure out how to dual boot it. This is just a side-bar, it’s not really relevant to anything but this specific device.)

With patches

I bought a weird RK3399 mini PC a while ago, which does not seem to be fully supported by U-boot, but similar boards from the same company are supported. I’ve not fully explored this yet, I need to figure out how to get the board to pretend there’s no onboard EMMC because it’s seemingly broken in a way that hangs any attempts to boot Linux - but I have done this one thing. You can provide .patch files to the Nix build configuration by using extraPatches within the override.

nix-build -E "
with import <nixpkgs> { };
pkgsCross.aarch64-multiplatform.ubootROCPCRK3399.override {
  filesToInstall = [ \"spl/u-boot-spl.bin\" \"u-boot.itb\" \"idbloader.img\" \"u-boot-rockchip.bin\" \"u-boot-rockchip-spi.bin\" ];
  extraPatches = [
    ./plus-pro-patch.patch
  ];
}"

where plus-pro-patch.patch is a patch file made by pulling U-boot’s repository, making the change to the roc-pc-rk3399_defconfig file, committing it, and then running git diff > plus-pro-patch.patch:

diff --git a/configs/roc-pc-rk3399_defconfig b/configs/roc-pc-rk3399_defconfig
index cfcce408c8c..a8e992bf876 100644
--- a/configs/roc-pc-rk3399_defconfig
+++ b/configs/roc-pc-rk3399_defconfig
@@ -8,7 +8,7 @@ CONFIG_SF_DEFAULT_SPEED=30000000
 CONFIG_ENV_SIZE=0x8000
 CONFIG_ENV_OFFSET=0x3F8000
 CONFIG_ENV_SECT_SIZE=0x1000
-CONFIG_DEFAULT_DEVICE_TREE="rockchip/rk3399-roc-pc"
+CONFIG_DEFAULT_DEVICE_TREE="rockchip/rk3399-roc-pc-plus"
 CONFIG_DM_RESET=y
 CONFIG_ROCKCHIP_RK3399=y
 CONFIG_ROCKCHIP_SPI_IMAGE=y
@@ -21,7 +21,7 @@ CONFIG_SPL_SPI_FLASH_SUPPORT=y
 CONFIG_SPL_SPI=y
 CONFIG_DEBUG_UART=y
 # CONFIG_ANDROID_BOOT_IMAGE is not set
-CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399-roc-pc.dtb"
+CONFIG_DEFAULT_FDT_FILE="rockchip/rk3399-roc-pc-plus.dtb"
 CONFIG_DISPLAY_BOARDINFO_LATE=y
 CONFIG_SPL_MAX_SIZE=0x40000
 # CONFIG_SPL_RAW_IMAGE_SUPPORT is not set
@@ -88,3 +88,7 @@ CONFIG_DISPLAY=y
 CONFIG_VIDEO_ROCKCHIP=y
 CONFIG_DISPLAY_ROCKCHIP_HDMI=y
 CONFIG_ERRNO_STR=y
+# MANUALLY ADDED
+CONFIG_PCI=y
+CONFIG_CMD_PCI=y
+CONFIG_NVME_PCI=y

(I may not be recalling this exactly correctly, I’ve got this on the back-burner because I’ve enough Rockchips for a lifetime, but this was the basic technique.)

Hopefully this helps people feel more capable of bringing up a ‘mainline’ U-boot on ARM platforms. I personally have found it rather convenient, once I actually figured out what was going on.

>> Home