-
Notifications
You must be signed in to change notification settings - Fork 0
Decision Log
Key architectural and technology decisions made throughout this project, and the reasoning behind them. These are the "why did you choose X over Y?" answers.
Decision: Use MergerFS to pool individual disks into a single namespace, with SnapRAID providing file-level parity protection.
Reasoning:
- Flexibility — disks can be different sizes and added/removed independently
- Simplicity — MergerFS is a FUSE filesystem, no special kernel modules or pool management
- No vendor lock-in — each disk is a standard ext4 filesystem, readable individually even if MergerFS or SnapRAID is removed
- Failure isolation — if one disk dies, you only lose the data on that disk; SnapRAID parity allows recovery without the stress of a traditional RAID rebuild
- Cost — can add disks one at a time as budget allows, no need for matched sets
- Parity without real-time overhead — SnapRAID calculates parity on a schedule (not real-time), so there's no write performance penalty during normal use
- Separation of concerns — MergerFS handles pooling, SnapRAID handles protection; each can be changed independently
Trade-offs accepted:
- SnapRAID parity is snapshot-based (scheduled sync) — data written between syncs is unprotected
- Performance is limited to single-disk speed per file (fine for media streaming)
- Two tools to understand instead of one (ZFS does pooling + redundancy in a single solution)
Decision: Use Azure KeyVault as the source of truth, with GitHub Secrets only holding access credentials.
Reasoning:
- Shared access — both repos pull from the same vault (no duplicating secrets)
- Audit trail — KeyVault logs access events; GitHub Secrets don't
- Rotation — update once in KeyVault, both repos pick it up next run
- Scale — GitHub Secrets are limited per-environment / repo; KeyVault scales to hundreds
- Separation — the "keys to the safe" (SPN creds) are separate from "what's in the safe"
Trade-offs accepted:
- Azure dependency (but it's free tier eligible and extremely reliable)
- Slightly more complex initial setup (one-time cost)
Decision: Raw WireGuard tunnel from GitHub Actions runner to home network.
Reasoning:
- No third-party dependency — no Tailscale account, no Cloudflare account
- Deterministic — the tunnel config is static and self-contained in one secret
- Speed — WireGuard connects in milliseconds, critical for CI/CD speed
-
Simplicity — one config file, one command (
wg-quick up)
Trade-offs accepted:
- Requires a static endpoint (or dynamic DNS) for the home WireGuard peer
- Config is all-or-nothing (no granular ACLs like Tailscale offers)
- No web UI for management
Decision: Use LVM snapshots to capture Docker data with minimal container downtime.
Reasoning:
- Minimal downtime — containers are stopped only for the instant it takes to create the snapshot (seconds, not minutes)
- Consistency — snapshot captures a point-in-time view; no torn writes
- Already available — Ubuntu Server default install uses LVM, so no additional tooling
- Simple — create snapshot, tar it, delete snapshot. No backup agent to install
Trade-offs accepted:
- Snapshot space (100G) must be provisioned
- If snapshot fills before backup completes, it's invalidated
- Only protects
/docker— OS is rebuilt from code, not backed up
Decision: Separate nas-as-code (OS provisioning) from nas-containers-as-code (Docker stacks).
Reasoning:
- Different cadence — container stacks change frequently (new services, config tweaks); OS provisioning changes rarely
- Different blast radius — a bad container deploy is a restart; a bad OS change could brick the NAS
- Independent pipelines — containers can redeploy without re-running the full Ansible provisioning
- Separation of concerns — OS team vs app team thinking (even if it's just me 😅)
Trade-offs accepted:
- Cross-repo references for shared concerns (secrets, networking)
- Two wiki locations to maintain
- Slightly more complex initial setup
Decision: Use Ansible (not Terraform/Bicep) for NAS configuration.
Reasoning:
- Ansible is designed for configuration management — it excels at "make this server look like this"
- Terraform is for infrastructure provisioning — creating cloud resources, not configuring bare-metal servers
- Agentless — Ansible uses SSH, no agent to install on the NAS
- Idempotent — run it repeatedly, same result
- Mature ecosystem — modules for apt, systemd, files, mounts all built-in
The Azure KeyVault could be Terraform-managed, but since it's a one-time setup that rarely changes, the manual az commands were simpler.
Overview
Secrets & Networking
Ansible
- Ansible Structure
- Role: Docker
- Role: Storage
- Role: Samba
- Role: NUT Client
- Role: Backup
- Role: Coral TPU
Pipelines
Operations