main
..
rw-r--r--
19 B
rw-r--r--
2.9 KB
rwxr-xr-x
4.3 KB
rwxr-xr-x
1.5 KB
rwxr-xr-x
2.2 KB
rwxr-xr-x
5.2 KB
rwxr-xr-x
7.9 KB

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

  1. Generate — Nix evaluates globals.nix to produce DNS zone files and WireGuard configs (symlinked into generated/)
  2. 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
  3. Boot — RPi firmware loads U-Boot → kernel + initramfs → entire OS in RAM
  4. Persist — A small ext4 partition (label=persist) holds SSH host keys and WireGuard private keys, mounted at boot by the persist-keys init 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.prev on the boot partition