Cross-Compiling ZeroClaw for Raspberry Pi on NixOS
2026-02-19
I have zeroclaw that I wanted to run on a Raspberry Pi 5 (Ubuntu 24.04 LTS, aarch64). My development machine is a NixOS x86_64 desktop. The goal: compile on the desktop, deploy to the Pi—without touching the Pi’s package manager or installing a Rust toolchain there.
This post documents how I got it working using a Nix devShell, and the several issues that came up along the way.
The Plan: devShell, not a Nix derivation
The first design decision was how to drive the cross-compilation inside Nix. Two options exist:
- Nix derivation/package — let Nix build and manage the output as a store path.
- devShell — enter a shell that has the cross-toolchain on
PATHand runcargo buildmanually.
I chose the devShell approach. zeroclaw is not a Nix-managed package, it lives
at ~/zeroclaw/ as a normal checkout. I just needed the right compiler
environment available when I ran cargo build.
Setting Up the devShell
I added a zeroclaw-cross entry to the devShells output in flake.nix. The
key ingredients:
pkgsCrossinstantiated foraarch64-multiplatformvialib.systems.examples.aarch64-multiplatformrust-overlayoverlays threaded through sorust-bin.stable."1.92.0"(the version pinned in zeroclaw’srust-toolchain.toml) is available- The aarch64 GCC cross-toolchain from
pkgsCross.stdenv.cc opensslandpostgresqlfrompkgsCross.pkgsStaticfor static linking
The relevant env vars exposed in the shell:
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER =
"${pkgsCross.stdenv.cc}/bin/aarch64-unknown-linux-gnu-gcc";
CC_aarch64_unknown_linux_gnu =
"${pkgsCross.stdenv.cc}/bin/aarch64-unknown-linux-gnu-gcc";
CXX_aarch64_unknown_linux_gnu =
"${pkgsCross.stdenv.cc}/bin/aarch64-unknown-linux-gnu-g++";
AR_aarch64_unknown_linux_gnu =
"${pkgsCross.stdenv.cc}/bin/aarch64-unknown-linux-gnu-ar";
Because forAllSystems in my flake only covers x86_64-linux and
x86_64-darwin, the shell was added outside that helper using a // merge on
the devShells output:
devShells = forAllSystems (...) // {
"x86_64-linux" = devShells."x86_64-linux" // {
zeroclaw-cross = pkgs.mkShell { ... };
};
};
the full flake.nix is here.
Then entering the shell and building is just:
nix develop .#zeroclaw-cross
cd ~/zeroclaw
cargo build --release --target aarch64-unknown-linux-gnu
Issue 1: C crates ignoring the cross-compiler
The first build attempt failed midway with ARM assembly errors inside blake3
and aws-lc-sys. The error looked roughly like:
error: unknown directive
.arch armv8-a
This is the classic sign that the C compiler used by the cc crate is the
host gcc, not the cross gcc. Setting only
CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER is not enough. That variable
tells Cargo which linker to use for the final link step, but crates that compile
C/C++/asm via the cc crate look at a different set of env vars:
CC_aarch64_unknown_linux_gnu
CXX_aarch64_unknown_linux_gnu
AR_aarch64_unknown_linux_gnu
Adding all four variables to the devShell fixed the issue. The build then completed in about 3.5 minutes.
Issue 2: Binary hardcoded to the Nix store interpreter
With the binary built, I copied it to the Pi:
scp ~/zeroclaw/target/aarch64-unknown-linux-gnu/release/zeroclaw \
ubuntu@192.168.1.109:~/.cargo/bin/zeroclaw
Running it on the Pi gave:
bash: /home/ubuntu/.cargo/bin/zeroclaw: cannot execute: required file not found
Checking the ELF interpreter with readelf revealed the problem:
[Requesting program interpreter: /nix/store/ghvpbda663vmf62g22d9y47z55yh43xn-glibc-aarch64-unknown-linux-gnu-2.42-47/lib/ld-linux-aarch64.so.1]
The binary was built against the Nix glibc, so its dynamic linker path
points into /nix/store/—a path that simply doesn’t exist on Ubuntu. The fix is
patchelf, which rewrites the ELF header in-place:
nix shell nixpkgs#patchelf -- patchelf \
--set-interpreter /lib/ld-linux-aarch64.so.1 \
--set-rpath "" \
~/zeroclaw/target/aarch64-unknown-linux-gnu/release/zeroclaw
After patching, readelf -l showed the standard Ubuntu interpreter path, and
the binary ran on the Pi straight away:
$ zeroclaw --version
zeroclaw 0.1.0
Summary
The full flow that worked:
- Add a
zeroclaw-crossdevShell toflake.nixwithpkgsCrossforaarch64-multiplatformand all four cross-compiler env vars set. - Enter the shell and run
cargo build --release --target aarch64-unknown-linux-gnuinside the project directory. - Patch the ELF interpreter with
patchelf --set-interpreter /lib/ld-linux-aarch64.so.1. scpthe patched binary to the Pi.
Two non-obvious gotchas to keep in mind:
- The
cccrate usesCC_<target>/CXX_<target>/AR_<target>, not the Cargo linker variable—you need all of them for crates with C/asm code. - Nix-built binaries embed the Nix store path as the ELF interpreter. Always
patchelfbefore deploying to a non-Nix system.