Everything I run — servers, workstations, a laptop, a WSL install, and the Android phone in my pocket — is described by a single Nix flake. Twenty-one hosts, one hosts/default.nix listing them, one common/ directory holding everything they share. make switch HOST=<name> and the machine converges on what the repo says it should be.
This post is the overview: what’s running and how the fleet is shaped. The parts that need real room to explain have their own posts, indexed at the bottom.
Services
Before the internals, the services the fleet runs.
Open to anyone:
| Service | Function |
|---|---|
| Blog | Jekyll static site, served behind Traefik on the Matrix VPS. |
| Matrix homeserver | Federated messaging with Synapse behind Traefik. |
| Element | The web client for the Matrix homeserver. |
| Bluesky PDS | A self-hosted personal data server. |
| Signed apt repository | Vendor .debs that ship no repo of their own — metadata only, pointing at upstream download URLs. |
Sign-in required:
| Service | Function |
|---|---|
| Coder | The control plane for my dev workspaces; the machines that actually run them are elsewhere in the fleet. |
| Netmaker | The dashboard for the mesh VPN that connects the fleet. |
| Portainer | Management for the container stack on the host below. |
| Karakeep | Bookmarks and read-later, self-hosted. |
| Grafana | Monitoring the fleet. |
Nearly all of that shares a single small container, which is a slightly absurd amount of load for an OpenVZ guest on a 3.10 kernel and works fine.
What’s not in either table is the shell access: SSH to every host in the fleet reaches the internet only through Cloudflare tunnels, so there is no listening port to publish in the first place. That asymmetry is deliberate — the interesting engineering in this setup is in tunnels, meshes and grafting rather than virtual hosts.
The fleet
By type, as the flake partitions them:
| Type | Count | What they are |
|---|---|---|
server |
12 | Cloud VMs and a couple of bare-metal boxes, spread across Asia, Europe and the US |
non-nixos |
6 | Stock Ubuntu machines, plus one OpenVZ container, managed by home-manager alone |
desktop |
1 | A 2009 iMac (yes, really) |
wsl |
1 | NixOS-WSL for Windows work |
android |
1 | This phone |
Only fourteen of them run NixOS. That turns out to be the interesting thing about the setup: the engineering effort isn’t in the NixOS hosts, it’s in everything else. A NixOS module and a home-manager module that produce the same behaviour on stock Ubuntu need to agree, so the shared logic sits in pure-data modules underneath both — one source of truth, two renderers. When the Ubuntu box needs a firewall rule that its NixOS sibling gets from networking.firewall, the same data goes through a generator that writes a nushell script into /etc/ufw/after.init instead.
On those six non-NixOS hosts there’s one decision worth stealing. /nix/var/nix/profiles/default points at root’s home-manager profile, so pkgs.nix in root’s home.packages is the machine’s Nix, and its version is a property of flake.lock. The nix-daemon units are rendered by home-manager with a profile-indirect ExecStart. Upgrading Nix on a machine that doesn’t run NixOS becomes make home-root HOST=<h> and a daemon restart.
Deep dives
Three parts of this needed more room than an overview allows:
- Nix on a Phone, and the Trick That Makes It Work — running a full Nix userland on Android, and the base-32-to-base-64 hash re-encoding that keeps store paths byte-identical in length while pointing somewhere else entirely.
- A 2009 iMac That Refuses to Die — inverted SSH ciphers on a CPU with no AES-NI, a measured 92 MiB kernel diet, an ext4 patch with an upstream submission kit, and swap over NVMe-over-TCP.
- Pins, Secrets, and Deploys That Can’t Strand You — seven nixpkgs branches each pinned for a stated reason, secrets without agenix, and two mechanisms that stop a remote switch from bricking the box.
Still to write: the cross-border connectivity layer — mesh VPN, reverse tunnels, multipath TCP — which is the other half of the repo.
What the repo is actually for
The thing I’d most want to convey isn’t any single mechanism. It’s a habit: in this repo, comments don’t say what the code does. They say what broke.
[host] demonstrated exactly this (“Failed to open …/nix-daemon.socket” three seconds before nix.mount).
1690/1693 bin-farm symlinks are 60–90 B targets.
measured: 0
kvm:kvm_userspace_exitin 10 s.
the gate boot missed by EXACTLY TWO MESSAGES.
Every one of those is a debugging session someone (me) already paid for, written down at the exact line where the next person would otherwise repeat it. Nix is what makes the fleet reproducible. The comments are what make it maintainable — they’re the difference between a configuration I can still change in two years and one I’d be afraid to touch.
That, more than the twenty-one hosts, is the infrastructure.
