Framework: Zola
Hosting: Github Pages
Theme: Consoler Dark
Title: parkerjones.dev

Nix Fleet, Part 4: Installing a Helios64 NAS from Bare Metal with nixos-anywhere and disko

2026-06-26
Parker Jones and Claude Opus 4.8 in homelab
#nix , #nixos , #disko , #nixos-anywhere , #zfs , #helios64 , #arm , #nas and #homelab
5 minute read

Nix Fleet, part 4. The series so far: the one-flake architecture, remote builders, and fleet-wide secrets. This part is where it all gets pointed at bare metal — a 5-bay ARM NAS, installed declaratively and reproducibly.

The Kobol Helios64 is a 5-bay NAS built on a Rockchip RK3399 (aarch64, 4–8 GB RAM). It's a lovely, slightly cantankerous little ARM board, and it's the storage backbone of my homelab. The goal for this host was the same as every other machine in the fleet: I should be able to reinstall it from the flake and get an identical result, disks and all. No clicking through an installer, no hand-partitioning, no "what did I configure last time."

That's what disko and nixos-anywhere make possible.

The two tools

Together they collapse "install a NAS" into one command:

nix run github:numtide/nixos-anywhere -- --flake .#helios64 <ssh-host>

That partitions the drives per the disko spec, installs the helios64 host config from my flake, and reboots into a running NixOS. The disk layout is no longer a one-time manual act I'd have to remember — it's part of the host definition.

The ARM-specific bits

An ARM SBC NAS has a few wrinkles a generic x86 box doesn't, and the host config names them explicitly:

# hosts/nixos/helios64.nix
{
  networking.hostName = "helios64";

  # The RK3399 needs its device tree to boot correctly
  hardware.deviceTree.enable = true;
  hardware.deviceTree.name = "rockchip/rk3399-kobol-helios64.dtb";

  services.openssh.enable = true;
  system.stateVersion = "24.11";
}

The device tree is the load-bearing line — without rk3399-kobol-helios64.dtb, the board doesn't come up right. The boot chain itself is U-Boot/Tow-Boot on SD/eMMC with the NixOS root on SATA. And because this is aarch64-linux, the system image builds on my x86 box via the emulation I set up in Part 2 — I never need a native ARM build host just for the NAS.

A note on honesty: the committed host config is intentionally minimal right now (it even evaluates as a container in CI to bypass boot/filesystem asserts). Storage and services are phased work. So treat this post as the design and install method I'm executing against, not a claim that every service below is already live. The series documents the build as it happens.

Storage: ZFS, and why

The storage decision got its own ADR (I keep architecture decision records in the repo — docs/adr/0001-choose-zfs-for-helios64.md, accepted 2025-08-27). The short version:

Decision: ZFS. End-to-end checksums, snapshots, send/receive, and self-healing are worth a lot for data I actually care about, and NixOS's ZFS integration is mature. The cost is memory pressure on a low-RAM ARM board and tighter kernel/module version coupling. The alternative considered — mdraid + ext4/btrfs — is simpler and lighter but has weaker integrity guarantees. For a NAS, integrity wins.

The pool config reflects the board's constraints:

boot.supportedFilesystems = [ "zfs" ];
services.zfs = {
  autoScrub.enable = true;
  trim.enable = true;
};
# On a 4 GB board, cap the ARC so ZFS doesn't starve everything else:
# boot.kernelParams = [ "zfs.zfs_arc_max=1073741824" ]; # 1 GiB

Sensible defaults for the pool (tank): ashift=12, compression=zstd, autotrim=on, atime=off for bulk data, and a topology chosen by disk count — mirror for 2 disks, RAIDZ1 for 3–5, RAIDZ2 when I want to survive two failures. Datasets split by purpose: tank/data/shares for SMB, tank/data/media for streaming, tank/backups for restic targets (with credentials supplied by agenix — that's the payoff of Part 3).

The gotchas this board taught me

Real hardware has opinions, and the runbook records them so I don't relearn them at 2am:

Why the ceremony is the point

It would be faster, once, to flash an SD card and click through a setup. But "once" is the trap. The value of the disko + nixos-anywhere + flake approach is the second time — when a disk dies, or I want to rebuild on fresh drives, or I'm reproducing the NAS to test an upgrade. Then the disk layout, the ZFS topology, the device tree, the services, and the secrets are all declared, and the rebuild is a command, not an archaeology project.

That's the whole thesis of this series, taken to its hardest case. Part 1 claimed one flake could configure every machine — laptop, server, and NAS — reproducibly. The NAS is the machine where reproducibility is least convenient and most valuable, and it's the one that proves the model. Same flake, same discipline, all the way down to the spinning rust.

The host config and ADRs referenced here live in parallaxisjones/dotfiles.

— Parker Jones, parkerjones.dev