The RTL8156B USB 2.5GbE NIC uses the mainline r8152 driver (drivers/net/usb/r8152.ko.gz), which is unaffected by the non-Intel ethernet vendor pruning step — that only removes subdirectories under drivers/net/ethernet/. However, the r8152 driver requests rtl8156b-2.fw at probe time, which was absent because only linux-firmware-intel and linux-firmware-i915 were installed. Add linux-firmware-rtl_nic to supply it. The firmware files are pre-compressed .fw.zst, so the size impact is ~57 KB on the final initramfs. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Alpine Bare-Metal Homelab OS
Summary
This is a largely (though not wholly, like this summary chunk) AI driven series of scripts/tools used to generate a initrd and vmlinuz using alpine as the userspace, for being ran on n100 nodes. We rely on the nodes booting with iPXE (the ipxe script is also created by this project), and an RB4011 used to hold the vmlinuz and initrd. This is so we can use nodes as "cattle" instead of "pets", so any new hardware that boots into ipxe will automatically join the "herd".
We try to keep the initrd as small as possible so nodes both boot fast, and so it fits on small space on the RB4011. As of this commit, it's a meager ~159 MB initrd and ~13MB vmlinuz. This contains a VM runner, podman for containers, ssh server, zfs (we run from RAM but one node wants ZFS), etc.
The RTL8156B USB 2.5GbE NIC is supported via the mainline r8152 kernel module
(in drivers/net/usb/, safe from the non-Intel ethernet vendor pruning step).
Its firmware (rtl8156b-2.fw) is provided by linux-firmware-rtl_nic, which
adds only ~57 KB to the image (files are pre-compressed .fw.zst).
We have a bash driven initrd/etc generation, with configuration in config.sh for things like what alpine version.
Build
To create the initrd and vmlinuz and ipxe script;
# First build (downloads ~500 MB, cached for offline rebuilds)
./build.sh
# Rebuild initramfs without re-downloading packages
./build.sh --no-rootfs
# Override boot server URL for the iPXE script
BOOT_SERVER_URL=http://10.0.0.1 ./build.sh
To locally test (manually feeding vmlinuz and initrd to qemu);
# Text mode (serial console) — press Ctrl-a x to quit
./start_qemu.sh
# Graphical mode
./start_qemu.sh --graphical
# More RAM / CPUs
./start_qemu.sh --memory 4G --smp 4
Offline / Reproducible Builds
The cache/ directory stores every downloaded artifact:
cache/apk-tools/—apk.staticbinary and Alpine signing keyscache/keys/— Alpine APK signing keyscache/apk-packages/— All downloaded.apkfiles
After a successful online build, copy the entire project directory (including
cache/) to an air-gapped machine and rebuild without internet.
Overlay Files
Drop files into overlay/ to include them verbatim in the rootfs. The
directory structure mirrors /. For example:
overlay/etc/ssh/authorized_keys -> /etc/ssh/authorized_keys
overlay/root/.bashrc -> /root/.bashrc
Space Savings
The initrd started at ~181 MB (alpine-base + firecracker), grew to ~229 MB after adding Nomad, and is now back down after the analysis below. All measurements are on a 473 MB uncompressed cpio of the rootfs.
Compression
| Config | Size | vs gzip-9 | vs zstd-19 | Time |
|---|---|---|---|---|
| gzip -9 (reference) | 266 MB | baseline | +16% | 32s |
| zstd -1 -T0 | 277 MB | +4% | +21% | <1s |
| zstd -6 -T0 | 259 MB | -3% | +13% | <1s |
| zstd -9 -T0 | 246 MB | -8% | +7% | 1s |
| zstd -15 -T0 | 244 MB | -8% | +7% | 4s |
| zstd -19 -T0 (was default) | 229 MB | -14% | 0% | 14s |
| zstd -22 -T0 | 222 MB | -17% | -3% | 148s |
| zstd -19 -T0 --long=24 | 227 MB | -15% | -1% | 25s |
| zstd -19 -T0 --long=25 | 226 MB | -15% | -1% | 26s |
| zstd -19 -T0 --long=26 | 225 MB | -15% | -2% | 28s |
| zstd -19 -T0 --long=27 (current) | 223 MB | -16% | -3% | 29s |
| zstd -22 -T0 --long=27 | 222 MB | -17% | -3% | 144s |
--long=27 extends the match window to 128 MiB. It saves 6 MB vs the plain
-19 default for only 2x compression time, and matches -22 --long=27 for
a fraction of the cost. -22 alone barely beats -19 --long=27 and costs
10x longer.
Pre-compression rootfs stripping
These are rootfs savings (before compression). Kernel modules and firmware files are already zstd-compressed by Alpine, so stripping them saves roughly their full uncompressed size in the final initrd.
| What | Savings | Rationale |
|---|---|---|
strip --strip-debug on nomad binary |
~23 MB | HashiCorp ships with DWARF debug info |
strip --strip-debug on firecracker + jailer |
~1.5 MB | Also shipped with debug info |
kernel/drivers/net/wireless |
~11 MB | N100 has no WiFi radio |
lib/firmware/i915 |
~9 MB | GPU driver (kernel/drivers/gpu) already stripped |
intel/vsc + intel/ipu |
~8 MB | Vision/camera subsystems, not on a headless server |
Intel Bluetooth (intel/ibt-*) |
~5 MB | No BT use on a server node |
| Non-Intel ethernet vendors | ~5.5 MB | Keep only ethernet/intel (e1000e/igb/ixgbe) |
| Legacy filesystems (reiserfs, ocfs2, hfs, jfs, …) | ~2.5 MB | ext4, overlayfs, btrfs, xfs, fat, nfs, fuse, squashfs kept |
net/wireless, net/tipc, net/rxrpc, net/x25 |
~1.5 MB | Unused network protocol stacks |
intel/avs + intel/ish |
~0.9 MB | Audio DSP + sensor hub, not on a server |
usr/share/bash-completion |
~1.3 MB | Shell completions not needed in initramfs |
| Total pre-compression | ~69 MB off rootfs | → ~55–65 MB off final initrd |
Original Prompt
The following is the original prompt that initiated this project, preserved verbatim for bread crumb purposes. Note we diverged from this over time, but much of the original philosophy still stands.
I want to use Alpine as the OS running bare metal on a few Intel N100 based nodes in my homelab. Thing is, I want all the nodes to boot using iPXE, and make use of alpine because it allows for an extremely minimal base to start from. The bare metal OS will be responsible for starting up VM's (lets assume firecracker as the VM execution engine) and containers (podman as the execution engine). Each N100 node must also have an OpenSSH server running, a git client, and use bash as the shell. Ah, and support for the zfs file system. I want to be able to generate images which can be used by iPXE to boot, and manually specify what packages to have embedded in Alpine. Let me specify the Alpine release. I want to be able to reproduce this without an internet connection, so ensure there is some caching mechanism in place (a CACHE directory for example) that contains everything needed to create the image. Lastly, add a "start_qemu" (or firecracker if that would be better/simpler for this purpose) solely to ensure the resulting initrd and vmlinux files passed to iPXE actually work (ideally in a text only mode, but allow for graphical). Oh, and create a CLAUDE.md meant for this type of project, which includes a copy of this prompt word for word (for auditing purposes).