I’m writing this blog post to document a successful NixOS setup on my Raspberry Pi 4 in case I somehow break it and because the issues I encountered were not covered in other blog posts or wiki pages.

This is not a step-by-step tutorial, reading until the end will allow you to skip a lot of issues.

Material

  • Raspberry Pi 4 (or 5)
  • micro SD card or USB storage (this will be the location of /nix/store)
  • External display + keyboard (to enable SSH, you’ll need to build your own image from this)

I’ll use a USB SSD because my Raspberry Pi’s SD card reader broke a long time ago (I get a nice warning about wrong voltage in the card reader now…).

Booting the Raspberry Pi 4 with USB may require a firmware update beforehand. From Raspberry Pi OS, simply run: sudo rpi-update

Get the image

First, we’ll get the NixOS image. This is the one I used at the time of writing, you should have a similar experience if you use it too.

wget https://hydra.nixos.org/build/295616998/download/1/nixos-image-sd-card-25.05pre788136.8a2f738d9d1f-aarch64-linux.img.zst
unzstd -d nixos-image-sd-card-25.05pre788136.8a2f738d9d1f-aarch64-linux.img.zst

See here for more recent versions.

Why this specific image

If you’re wondering why I’m using an “sd-card” image even though I boot from USB, it’s because this image is designed for the Raspberry Pi. It directly holds the system instead of containing an installer. See the related nix module:

This module creates a bootable SD card image containing the given NixOS configuration. The generated image is MBR partitioned, with a FAT /boot/firmware partition, and ext4 root partition. The generated image is sized to fit its contents, and a boot script automatically resizes the root partition to fit the device on the first boot.

The firmware partition is built with expectation to hold the Raspberry Pi firmware and bootloader, and be removed and replaced with a firmware build for the target SoC for other board families.

If I read this before, I could have just used the module to make an SD card image from my config. But I still needed some experimentation to get a working config anyway.

Install the image

Find out your device name with lsblk.

sudo dd if=IMAGE.img of=/dev/sdX bs=4096 conv=fsync status=progress

Once the image is written, we can boot the Raspberry Pi up.

Problems

No nix channel

The very first nix related command I ran gave me this kind of error:

$ nix-shell -p git
warning: Nix search path entry '/nix/var/nix/profiles/per-user/root/channels' does not exist, ignoring
error: file 'nixpkgs' was not found in the Nix search path (add it using $NIX_PATH or -I)

       at «string»:1:25:

            1| {...}@args: with import <nixpkgs> args; (pkgs.runCommandCC or pkgs.runCommand) "shell" { buildInputs = [ (nix-info) ]; } ""
             |                         ^
(use '--show-trace' to show detailed location information)

Not nice, but probably fixable.

So I tried adding some channels:

$ nix-channels --add https://nixos.org/channels/nixpkgs-unstable
$ nix-channels --update
# Some very helpful errors
# ...
note: build failure may have been caused by lack of free disk space

Well… The nix module I showed you before said:

The generated image is sized to fit its contents, and a boot script automatically resizes the root partition to fit the device on the first boot.

Taking the SD card out and running lsblk on my desktop computer showed me that the script never ran:

$ lsblk
NAME         MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
...
sdc          8:32   1 119,1G  0 disk
├─sdc1       8:33   1    30M  0 part
└─sdc2       8:34   1   3,5G  0 part

So this turns out to be a problem within a problem.

No space available

Let’s then extend the partition. Still from my desktop computer:

$ sudo parted /dev/sdc
GNU Parted 3.6
Using /dev/sdc
Welcome to GNU Parted! Type 'help' to view a list of commands.
(parted) print
Model: Generic MassStorageClass (scsi)
Disk /dev/sdc: 128GB
Sector size (logical/physical): 512B/512B
Partition Table: msdos
Disk Flags:

Number  Start   End     Size    Type     File system  Flags
 1      8389kB  39,8MB  31,5MB  primary  fat16
 2      39,8MB  3803MB  3763MB  primary  ext4         boot

(parted) resizepart 2 100%
(parted) quit

Let’s make the filesystem use the new space:

sudo resize2fs /dev/sdc2

Alright, seems good now:

$ lsblk
NAME         MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
...
sdc          8:32   1 119,1G  0 disk
├─sdc1       8:33   1    30M  0 part
└─sdc2       8:34   1   119G  0 part

Deploying my configuration

We can finally install our channels and start downloading binaries from the nix cache.

I cloned my NixOS config in which I added a new host: dagon.

Not enough RAM

Well, turned out my config was too heavy even though I didn’t set any HomeManager config on purpose.

The OOM Killer literally broke the system, it didn’t boot anymore, I just decided to start over at this point.

Swapfile

Reduced some parts of the config, added a swapfile, rewrote the image.

sudo fallocate -l 4G /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

To add it to your NixOS config:

swapDevices = [{ device = "/swapfile"; size = 4096; }];

Remote building

If you enabled the SSH server and rebuilt the default config (in /etc/nixos/), you could remotely build your configuration over SSH from your desktop computer:

nixos-rebuild switch --flake .#HOSTNAME --target-host USERNAME@IP --use-remote-sudo

But note that if your computer is cross-compiling, you’ll have degraded build performance (but probably no RAM issue).

PAM fails, users get deleted, new install

In my config I had only one user : louis.

But the image used a nixos regular user and a root one.

Building as nixos broke everything, I believe both users got deleted during deployment, and I couldn’t use sudo anymore.

Well, here goes a new image.

I added a root user and was finally able to build my config.

No Ethernet

So this issue appeared right after I deployed my config, I spent some time trying to fix it, but it ended fixing itself after some hours. I think that the MAC address was bound to a specific IP in my router from the previous system I had on it. I tinkered with it to set up some services and may have fixed it by accident.

Happy End

I successfully built my config and deployed a few services. Nextcloud was way easier to install with Nix. I also deployed Glance that I used to self-host on my machines before.