Context
kuhree.com/platform is the NixOS configuration for every machine I run: eight hosts plus the 3D printer, from one flake. It replaced earlier versions called fleet and flake. v4, started in October 2026, rebuilt it from a blank flake. The goal was a system where adding a service is one reviewed file and machines can’t drift.
How a change ships
flowchart LR PR[Pull request] --> CI[nixbot CI] CI --> C{Every host evaluates<br/>VM test per service<br/>alert rule tests<br/>lint, DNS, secrets} C -->|merge| M[v4 branch] M --> H[Each host pulls it<br/>builds and switches] H --> N[Deploy reported<br/>to alerts]
- One service, one file. The flake is dendritic: each service file holds its NixOS module, DNS name, sign-in client, and VM test.
- Every change is checked. Self-hosted CI builds each host’s full system, runs a NixOS VM test for every service, unit-tests the alert rules, and checks formatting, DNS, and secrets config.
- Machines deploy themselves. After a merge, each host pulls the branch with comin, builds, and switches. A host that can’t build raises an alert.
- Updates come as pull requests. Renovate opens one each morning for flake inputs, pinned packages, and container images.
- Decisions are written down. More than 120 numbered decision records explain why the system looks the way it does. Comments in the code point back to them.
Stack
| Area | What |
|---|---|
| Hosts | NixOS, flake-parts, disko installs, LUKS with TPM unlock, root on tmpfs with explicit persistence |
| Deploys | comin (pull-based), Forgejo, nixbot CI, remote builders, a self-hosted binary cache |
| Network | Headscale mesh with Tailscale clients; private names resolve only on the mesh; a small public edge on a VPS |
| Identity | Kanidm single sign-on; an oauth2-proxy gate for apps without their own sign-in |
| Secrets | sops-nix with age; host keys decrypt only their own secrets |
| Cloud | OpenTofu for the VPS and DNS, state encrypted in the repo |
| Observability | OpenTelemetry Collector on every host; VictoriaMetrics, VictoriaLogs, VictoriaTraces, Grafana; vmalert and Alertmanager to ntfy; Gatus watching from outside |
| Storage and backups | ZFS for important data, bcachefs for bulk media; nightly restic per host, plus an offsite copy to Backblaze B2 from a ZFS snapshot |
What it runs
Source control and CI, sign-in, passwords, documents, photos, media, Home Assistant, search, local AI models (chat, image, speech) on an RTX 4070 Ti, the AI agents I use daily, and the 3D printer’s MCP server.
Status
Active. The repo lives on my private forge, and I’m happy to walk through it. Host addresses, credentials, and security boundaries aren’t covered on this page.
Lessons
- When every machine builds from one reviewed source, drift stops being a problem.
- A VM test per service catches more than any number of careful manual deploys.
- Pull-based deploys and outside health checks make it safe to change things from anywhere, AI agents included.
