NixOS Infrastructure-as-Code — Replicable Multi-System Base

NixOSInfrastructure as CodeNix FlakesReproducible SystemsPlatform Engineering
Image 1

What it is

A single NixOS flake that declaratively produces 16 system configurations from one shared codebase — desktop workstations, laptops, a public VPS, a ZFS NAS, Proxmox LXCs, and macOS. Every OS-level concern (kernel modules, user environment, services, security posture, AI agent configuration) is versioned in git and rebuildable from source. No manual setup, no configuration drift, no "works on my machine."

Why it exists

I maintain nine machines of my own plus a multi-tenant homelab for another user. Before this flake, each machine drifted independently — installed packages diverged, security rules weren't uniform, and recovering a dead laptop meant hours of re-installing and re-configuring from memory. Different hardware (AMD/Intel/NVIDIA), different use cases (workstation/server/LXC/macOS), and different trust boundaries (public VPS vs. home LAN) made a "one script to rule them all" approach impossible with imperative tooling.

Nix solves the problem declaratively. The hard engineering is the composition layer: how to share 90% of the configuration across wildly different targets without hardcoding a single hostname check.

Architecture

The flake produces configurations for 16 hosts across four trust zones — public (Cloudflare → VPS), private site-to-site (WireGuard VPS ↔ pfSense), home LAN (VLAN-segmented), and an overlay mesh (Headscale) that links everything regardless of physical location.

Every node in this diagram is one NixOS (or nix-darwin) profile. All profiles derive from lib/defaults.nix and type-specific bases (personal/, homelab/, LXC-base, KOMI_LXC-base, VPS-base, MACBOOK-base, darwin/). Adding a new node is copy-a-base + override 20–50 feature flags.

What I built

  • Feature-flag architecture — every conditional lives in lib/defaults.nix as a flag (systemBasicToolsEnable, gpuType, sudoAskpassEnable, homelabDockerStacks, …). Modules consume flags; profiles set them. Zero hostname == "nixosaku" checks in shared code.
  • Profile inheritance treelib/flake-base.nix + lib/flake-unified.nix handle recursive merge from global defaults → type-base → host-specific overrides. Type bases live in profiles/*-base-config.nix; host configs inherit and override.
  • Security-first defaults — 18 modules in system/security/ (firewall, LUKS full-disk encryption, SSH hardening, fail2ban jails, Firejail sandboxing, polkit rules, Restic-scheduled backups, WireGuard server, DNS blocklist, egress auditing, Wi-Fi audit). Every profile inherits the full baseline; opt-outs are explicit.
  • Unified aku wrapper — system-wide command (aku sync | update | upgrade | gc | harden | soften) that normalizes common Nix operations so I never have to recall the right nixos-rebuild flags from any directory.
  • Zero-touch installerinstall.sh (1,156 lines) handles LUKS SSH-unlock key generation, hardware detection, UEFI/BIOS auto-detect, flake copying, Docker container setup, and first rebuild. Blank SSD → running Hyprland desktop in ~20 minutes.
  • Declarative Claude Code integrationuser/app/claude-code/claude-code.nix generates ~/.claude/settings.json from Nix: 82 allow rules (tight Bash/Read/Edit/Write/Grep globs), 34 deny rules (SSH keys, credential files, /etc/shadow, ~/.gnupg/, crypto wallets), PreToolUse/PostToolUse hooks for prompt-injection detection, MCP server wiring. Every machine on the flake runs the same AI agent with the same guardrails.
  • Path-scoped Claude rules — 6 rule files in .claude/rules/ (nixos.md, deployment.md, darwin.md, docs.md, sway.md, gaming.md) with paths: frontmatter. Claude Code auto-loads the relevant rule when editing files that match the glob — e.g. touching **/*.nix surfaces the NixOS rule (feature-flag patterns, LXC base + override, secrets import). Replaces the older "read the router first" pattern with glob-triggered context.
  • 32 custom operations skills in .claude/commands/ — declarative markdown slash commands for repeated infrastructure work (/deploy-lxc, /check-database, /manage-pfsense, /audit-infrastructure, /manage-nas, /wake-on-lan-nas, /network-performance, …). Each is a small shell/MCP body with a description; executable from any conversation context.
  • Multi-tenant LXC pattern — the KOMI_LXC-base + per-host overrides template lets me run a full replica homelab (database, mailer, monitoring, proxy, tailscale) for another user, inheriting the same security baseline without coupling our configs.

Results

  • 260 Nix files defining every system end-to-end, from kernel modules to application layer.
  • 16 flake profiles from one codebase: 3 desktops, 3 laptops, 2 personal LXCs, 5 multi-tenant LXCs, 1 VPS, 1 NAS, 1 macOS.
  • 18 security modules inherited by every profile unless explicitly disabled.
  • 1,495 commits of version-controlled infrastructure history — zero configuration drift between rebuilds.
  • 190 docs under docs/ · 6 path-scoped Claude rule files · 32 custom operations skills · 13 operator scripts under scripts/.
  • Zero hostname checks in shared modules — all conditional behavior routes through feature flags.

Stack

NixOS, Nix flakes, Home Manager, nix-darwin, systemd, Stylix, LUKS, Restic, fail2ban, Firejail, polkit, Python (docs generator), Bash (install + operator wrappers), Git, git-crypt.

Status

  • Repo: private — reference snippets available on request.
  • Running daily on: my workstations, laptops, a production VPS, a ZFS NAS, and 5 Proxmox LXCs for a multi-tenant homelab I maintain for another user.
  • Related portfolio entries: my-homelab (the running instance of this IaC), vps-wireguard (the networking layer that connects it).