main
Alpine Diskless Appliances
Alpine Linux diskless appliances for Raspberry Pi 4 homelab servers. The entire OS runs from an initramfs in RAM — the SD card is read-only after boot.
Hosts
| Host | Role | Packages |
|---|---|---|
| athena | DNS (BIND), WireGuard | bind, wireguard-tools |
| demeter | DNS, MQTT | bind, mosquitto, wireguard-tools |
| aix | File/backup server | wireguard-tools |
Architecture
alpine/
├── common/ # Shared across all hosts
│ ├── packages # Base package list
│ ├── setup.sh # Common OpenRC setup (runlevels, users)
│ └── root/ # Root filesystem skeleton (sshd, chrony, etc.)
├── hosts/<hostname>/ # Per-host overrides
│ ├── packages # Additional packages
│ ├── setup.sh # Host-specific services
│ └── root/ # Host-specific config files
├── generated/ # Nix-generated configs (gitignored)
│ ├── zones/ # DNS zone files from globals.nix
│ └── wireguard/ # WireGuard configs per host
├── images/ # Build output (gitignored)
├── build.sh # Build initramfs in container (aarch64)
├── build-inner.sh # Runs inside the Alpine container
├── deploy.sh # rsync update to running host
├── flash.sh # Flash SD card (partition + copy)
└── test.sh # QEMU testing
Usage
All commands via the top-level Makefile:
# Generate DNS zones + WireGuard configs from Nix expressions
make alpine/generate
# Build a host image (runs generate first)
make alpine/build/athena
# Build all hosts
make alpine/all
# Test in QEMU
make alpine/test/athena
make alpine/validate/athena
make alpine/stop/athena
# Deploy update to running host (rsync, needs reboot)
make alpine/deploy/athena
# Flash a new SD card
make alpine/flash/athena DEVICE=/dev/sda
How It Works
- Generate — Nix evaluates
globals.nixto produce DNS zone files and WireGuard configs (symlinked intogenerated/) - Build — A container (podman/docker) runs Alpine aarch64 via QEMU user-mode, installs packages into a rootfs, runs setup scripts, then packs everything into a cpio+gzip initramfs
- Boot — RPi firmware loads U-Boot → kernel + initramfs → entire OS in RAM
- Persist — A small ext4 partition (
label=persist) holds SSH host keys and WireGuard private keys, mounted at boot by thepersist-keysinit script
Secrets
Secrets (.age files) are decrypted at boot by the secrets OpenRC init
script using the host’s SSH ed25519 key. Same encrypted files as the NixOS
agenix setup, same keys.
Updating
- Rebuild image:
make alpine/build/<host> - Deploy:
make alpine/deploy/<host>(rsync + reboot) - Rollback: previous initramfs saved as
initramfs.prevon the boot partition