13°
Portada del artículo: Why I moved my whole home lab to NixOS: four machines, one repository and what it cost
NixOSInfrastructureAgentsHome lab

Why I moved my whole home lab to NixOS: four machines, one repository and what it cost

I had four machines with four different operating systems, services running in tmux, passwords inside unit files and several agent sessions touching everything at once. In nine days I moved them to NixOS with a single flake. I measured what it gives: a deploy takes 11 to 17 seconds, the same commit produces the same system hash on two different machines, and going back to the previous version takes 1.6 seconds. And I measured what it cost: 35 of 111 commits are fixes, every Python environment was lost and the router's DNS repointed 1,185 files on its own.

Efrain Garay 14 October 2026

Playing summary

Until October 6 I had four machines at home and four operating systems: a laptop with Windows 11, a server with Kali, a tower with Fedora 43 and another laptop with Ubuntu. Each had been set up by hand, at different times. The stream ran in four tmux sessions, a database password lived inside a systemd service file, ComfyUI used the system python with packages installed by hand, and rebooting the server meant being at the keyboard to unlock the disk.

That holds while one person touches the machines. It stopped holding when I started working with several agent sessions in parallel: one building reels, another running models, another on the blog’s benchmarks. Each installed whatever it needed wherever it needed it. After a while nobody, me included, knew for sure what was on each machine or why.

So between October 6 and 14 I moved everything to NixOS. Today the four machines come out of the same repository. This post is about why, how, what I measured and what it cost, which was not little.

Narrated: the four machines going from four systems to one repository, a measured deploy per machine, the same hash on two machines, going back in 1.6 seconds, what it cost and the dashboard app with the real power draw. The voice is synthetic; every figure is the measured one.Watch it in the reel viewer →
The swarm today: one flake, 4 NixOS machines
Mac · git push + nz-deployVPS · not NixOS yetflake nix-zerg5b90fc92,447 lines of Nix23 modulesvworkersecondary GPUmedia · voiceIntel Core i5-9400F · 6 threadsRTX 2060 SUPER · 8 GB · 16 GB RAMgeneration 31 · 6.7 GB · 33 svcfedoramain GPUimage and video · modelsAMD Ryzen 7 7800X3D · 16 threadsRTX 4070 Ti SUPER · 16 GB · 31 GB RAMgeneration 11 · 9.1 GB · 35 svclenovobackupslab containersIntel Core i5-8250U · 8 threadsno GPU · 12 GB RAMgeneration 11 · 10.5 GB · 21 svchuaweilight CPUservices and builds⏻ switched off: did not answer

■ declared in the flake ·■ outside it ·■ down · last line of each card: generation, system closure, services running

What changes with NixOS

On a regular distribution, the system is the result of everything that was done to it: every apt install, every file edited in /etc, every service started by hand. If I want to know how a machine is, I have to look at it.

NixOS works the other way around. The whole system is described in files: which packages there are, which services run and with what options, which users exist, how the disk is partitioned. nixos-rebuild switch builds that system and activates it. If something is not written down, it does not exist, and whatever I installed by hand on the system disappears on the next change.

A flake is the way to pin all of that in a repository: the exact version of the packages (in my case, nixpkgs pinned to 26.05.11275), my own modules and each machine’s configuration. Mine is called nix-zerg and has 2,447 lines of Nix in 23 modules.

For working with agents, this changes the rules. No session installs anything by hand. When one needs something on a machine, it adds it to the flake, commits and deploys. If it gets it wrong, the mistake stays in the git history, with its diff, and can be reverted.

By hand, machine by machine → declared in one repository
beforenow
  • Operating system
    4 different ones: Windows 11, Kali, Fedora 43 and Ubuntu
    NixOS 26.05 on all 4, the same pinned nixpkgs
  • Services
    the stream in 4 tmux sessions; a pre-copy died when logind closed the session
    declared systemd units, restarted automatically
  • Secrets
    passwords inside the units and 10 plaintext secrets on one disk
    10 secrets encrypted with agenix to each machine's key
  • Python
    ComfyUI on the system python with global pip
    one environment per app with its exact lock in the repo
  • Rebooting
    vworker needed its disk unlocked by hand at the console
    it boots on its own; if something fails, the previous generation is picked
  • Knowing what runs
    hand-made inventories before touching anything
    the deployed commit is on every machine and the panel flags any that differs

How the migration went

111 commits in 8 days since the first commit: 35 of them fixes
Oct 6lenovoWindows outNixOS from a channel28Oct 7vworkerKali → NixOS over kexec7 min11Oct 8vworkermigrationcomplete5Oct 9Oct 1014Oct 1112Oct 12fedorafull wipe5 h 50 min down17Oct 1324Oct 14huaweiUbuntu → NixOSover Wi-Fi

■ feat 52■ fix 35■ docs 14■ refactor 5■ chore 4■ perf 1

lenovo came first, on October 6: Windows out and NixOS installed from a channel, the old way. The next day I imported it into the flake, and the system built from the repository came out identical to the one already running.

vworker, the Kali server, was different. Instead of a USB stick I used nixos-anywhere: it boots an installer over kexec on top of the running system, partitions the disk with disko and installs. It took 7 minutes, from 20:44 to 20:51 on October 7. I had copied what mattered to the lenovo beforehand, and the machine came back with the same SSH key and the same address on the internal network, so the encrypted secrets worked on the first boot.

fedora was the machine with the most on it: the main GPU, the models, a test environment for a client and 38 Python environments. I wiped it completely on October 12. Before touching the disk I checked the backup against the original: 1,407,986 files without a single difference. The core was down for 5 hours and 50 minutes, within what I had estimated.

huawei, the last one, went in on October 14 with a custom installer that connects over Wi-Fi. The first two times it hung and had to be switched off and on by hand.

A deploy is a commit

Deploys are done by a short script, nz-deploy. It refuses to run if there are uncommitted changes or if the commit is not on GitHub, so everything running on a machine exists in the repository. Then it ships the code over ssh, records which commit it is and runs nixos-rebuild switch.

The path of a change: from a commit on the Mac to the running system and the dashboard
The path of a change: from a commit on the Mac to the running system and the dashboardMacgit repo: flake nix-zergnz-deploy <host>GitHubHEAD = origin/main?NixOS machinessh + git archive/root/nix-zerg-deploy + REVISIONnixos-rebuild switch/nix/store/<hash>-nixos-system→ /run/current-systemnew generationagenix: encrypted secrets/etc/zerg-revisionzerg-status → tablet
nz-deploy with the commit already deployed: median vworker 17.3 s, fedora 11.2 s, lenovo 14.2 s (5 runs each)
  1. 1clean tree and pushed?
  2. 2git archive HEAD
  3. 3ssh to the machine
  4. 4writes REVISION
  5. 5nixos-rebuild switch
  6. 6failed units?
0 s5 s10 s15 s20 s25 s30 svworker17.3 sfedora11.2 slenovo14.2 s

■ median · ● each run · | opening ssh alone

With the commit already deployed, each deploy took between 10.4 and 28.1 seconds. The medians of five runs were 11.2 seconds on fedora, 14.2 on lenovo and 17.3 on vworker. The first round was the slowest on all three machines, and from the third round on the times were even. Opening the ssh connection alone has a median under half a second; subtracting it, the medians become 10.9, 13.8 and 17.0 seconds. I did not separate how much of that is evaluating the configuration and how much is activating the system.

An honest note: the flake declares deploy-rs, which has a magic rollback: if a machine stops answering over SSH after a change, it goes back to the previous version on its own. But nz-deploy does not use it: it calls nixos-rebuild switch directly. Today, if a change breaks a machine’s network, going back is manual.

The same commit, the same system

The promise of NixOS is that the same configuration produces the same system. I tested it by evaluating the same commit on two different machines, vworker and fedora, and comparing the hash of the whole system of each of the four hosts.

The same commit, evaluated on fedora and on vworker: 4 of 4 system hashes identical
⌂ fedora
⌂ vworker
system of vworker
5z6dgqgzi96z…
═
5z6dgqgzi96z…
system of fedora
6kx7a4s4m852…
═
6kx7a4s4m852…
system of lenovo
r225ybk6xsmd…
═
r225ybk6xsmd…
system of huawei
8mx43jjd6h8j…
═
8mx43jjd6h8j…
+ a comment in a modulenothing changes
vworkerfedoralenovohuawei
+ one line with effect in vworkerchanges only vworker
vworkerfedoralenovohuawei
↻ what is running now = what git says3/3 equal
vworkerfedoralenovo

The four hashes came out the same on both machines, character by character. It is the hash of the evaluated system, the one Nix would build; I did not rebuild the systems to compare the binaries bit by bit. To know the hash really depends on the configuration I ran two controls. Adding a comment to a module changed nothing, because comments are not part of what gets built. Adding one line with effect to vworker’s file changed vworker’s hash and nobody else’s.

The test I cared most about is the last row: the hash of what is running right now on each machine is the same one you get evaluating the commit from git. On the first try it did not match, and I was worried until I understood why: the deploy writes the commit number into the system, so the dashboard can show it. Evaluating with that same value, all three match.

Going back in 1.6 seconds

Every nixos-rebuild switch leaves a numbered generation, and the earlier ones are not deleted: they stay on disk and in the boot menu. vworker is on generation 31, and it has not rebooted once in the 7 days since I installed it.

Generations: one step per nixos-rebuild switch; back to the previous one in 1.59 s on lenovo
31
vworker · since 2026-10-07
11
fedora · since 2026-10-12
11
lenovo · since 2026-10-06
↶ 1.59 s back · ↷ 1.60 s forward

■ current generation · ■ previous, still bootable · 3 of 3 round trips, 0 failed units

I measured going back on lenovo, the machine with the least at stake: switching the running system to the previous generation and back to the current one, three times. Going back took a median of 1.59 seconds and coming forward 1.60. In all three, everything ended as it was and no unit was left failed. That is the safety net I did not have with Kali or Fedora: if a change goes wrong, the previous version is one command away.

Replay at real speed on lenovo: generation 11 → 10 in 1.59 s, and back in 1.60 s
switch-to-configuration switch
11current

↶ 1.59 s · ↷ 1.60 s · 3/3 rounds, 0 failed units

What it cost

This part matters to me more than the previous ones, because most writing about NixOS leaves it out.

What it cost: 8 incidents, and 35 of 111 commits (32 %) are fixes
32 %35/111

of the commits are fixes

  • ●●● data or results at riskThe router's DNS returns malformed answers: rclone saw the Drive empty and 1,185 links were repointed on their own
  • ●●● data or results at riskPython environments do not travel: 38 on fedora and about 65 GB on vworker to rebuild
  • ●●○ hours lostAbout 450 GB of GPU models to download again on fedora
  • ●●○ hours lost/mnt/ai-fast became /data with no compatibility link: about 379 files with the old path on the Mac alone
  • ●●○ hours lostThe site's forecast repeated the October 12 analysis in the runs of the 13th and 14th, without a single error
  • ●●○ hours lostlenovo cut out for 18 h 52 min, with no shutdown sequence
  • ●●○ hours losthuawei stopped answering during a power sweep and has to be checked by hand
  • ●○○ frictionThere is no /bin/bash and no python on the PATH

35 of the flake’s 111 commits are fixes, and 12 of them are from the first day. Each one is something that did not work the way I expected.

The worst incident was not NixOS’s but the router’s. Its DNS returns malformed answers, and rclone’s resolver read them as an empty Drive. A nightly job, which repoints the library’s files when a copy disappears, repointed 1,185 of them at once. It had to be reverted, and a public DNS pinned on every machine.

What was not declared was lost, and that is what took the most work. Python environments are not copied: 38 on fedora and about 65 GB on vworker had to be rebuilt from package lists. About 450 GB of models had to be downloaded again. fedora’s data folder changed path, from /mnt/ai-fast to /data, and on my Mac alone about 379 files were left pointing at the old one.

I found one case while writing this post. The weather forecast the site publishes repeated the October 12 analysis in the runs of the 13th and the 14th, without a single error. The model’s Python environment did not exist and the script failed. But the next step read the last forecast on disk, which was the old one. I rebuilt it, and now a failure in that step is a visible error.

And the everyday stuff: NixOS has no /bin/bash, and python is not on the PATH unless it is declared. Scripts with #!/bin/bash stopped running until they were changed to #!/usr/bin/env bash.

There were two outages whose cause I did not confirm. lenovo cut out on October 13 at 23:23 and did not come back until 18:16 the next day: 18 hours and 52 minutes without a single log line, and the system’s last message is a normal session close, with no shutdown sequence. And huawei stopped answering today during a power test, and someone has to go check it by hand.

The app that sees everything

With four machines declared, I still needed to see what was happening without logging into each one. So the flake has a module of its own, zerg-status: a Python collector, with no dependencies, that runs on every machine and serves a dashboard made for the tablet. It shows load, memory, disks, GPU, which process uses the GPU and which service it belongs to, the GPU locks, every service and every container, and which commit is deployed. If a machine runs another commit, or changed its kernel and needs a reboot, the dashboard flags it.

Since the module is in the flake, adding a machine to the dashboard is one line. The VPS, which is not NixOS yet, runs the same collector by hand and listens only on the internal network.

The Enjambre dashboard on the tablet at 22:19 on October 14: vworker, fedora and lenovo up to date on commit 5b90fc97fd, fedora with its GPU at 92% and 290 W, and huawei not answering.

The real dashboard. I hid the service and container lists, the lock names, the sessions and the VPS card, because they name client and third-party projects.

The dashboard also stores each machine’s power draw, and that is where this chart comes from:

Power drawn per machine, CPU + GPU: medians vworker 25.6 W, fedora 52.6 W, lenovo 1.4 W
vworker3.1 h of data25.6 W median56.1 W p95198.5 W maxfedora2.9 h of data52.6 W median219.4 W p95321.6 W maxlenovo0.4 h of data1.4 W median3.8 W p959.4 W maxtop line of each band = 250 W

fedora, with the RTX 4070 Ti SUPER, has a median of 52.6 W and a 95th percentile of 219.4 W when the GPU works; the maximum was 321.6 W. vworker sits at 25.6 W and lenovo at 1.4 W. These are windows of a few hours, what the dashboard had stored at the time of the measurement, not a full day.

When I would recommend it and when not

I would recommend it if you have more than one machine, if several people or agents touch the same systems, or if you have already lost track of what got installed where. Knowing which commit runs on each machine, being able to go back in seconds and having every change in git is what, in my experience this week, made the swarm easier to maintain. I did not measure it: it is my judgement, not a number.

I would not recommend it if you have a single machine that already works and you do not plan to touch it, or if you depend on software that needs fixed paths like /usr/lib and have no time to fight with that. nix-ld solves a good part of it, but not all. And do not expect the migration to be clean: a third of my commits were fixes.

What this measurement does not cover

  • There is no measured “before”. The old machines never had metrics, so I cannot say how much faster or more stable this is; only what it measures today.
  • A single swarm, four machines that differ from each other. None of this generalizes to other setups.
  • huawei was off during the measurements and is not in the times or the power.
  • Deploy times depend on the network and on each machine’s load at that moment.
  • Power covers a few hours per machine (under one on lenovo), not a full day.

Measured on October 14, 2026 on four machines running NixOS 26.05 (nixpkgs 26.05.11275), flake nix-zerg at commit 5b90fc9: vworker (i5-9400F, RTX 2060 SUPER), fedora (Ryzen 7 7800X3D, RTX 4070 Ti SUPER), lenovo (i5-8250U) and huawei (Ryzen 5 3500U, off during the measurements).

Sources

Frequently asked questions

Why NixOS and not Ansible or Docker on Ubuntu?

Because in NixOS the whole system comes out of the configuration: packages, services, users, disks and secrets. With Ansible on Ubuntu the real state can drift from what is written as soon as someone installs something by hand, and Docker solves the applications but not the system that runs them. In my case, with several agent sessions working on the same machines, I needed the only valid state to be the one in the repository.

How long does a deploy take?

With the commit already deployed, the median of five runs was 11.2 seconds on fedora, 14.2 on lenovo and 17.3 on vworker, end to end: checking the tree is clean, shipping the code over ssh, nixos-rebuild switch and looking for failed units. The first round was slower, between 17.7 and 28.1 seconds.

How fast can you go back if something breaks?

On lenovo, switching the running system to the previous generation took a median of 1.59 seconds, and coming back to the current one another 1.60, over three round trips with no failed unit. If the machine does not boot, the boot menu keeps up to ten generations.

What do you lose when migrating?

Everything that was not declared. In my case, the Python environments (38 on fedora and about 65 GB on vworker), about 450 GB of models that had to be downloaded again, paths that changed and old services I chose not to port. Also, /bin/bash does not exist and python is not on the PATH, so several scripts had to be fixed.

Is it worth it with a single machine?

Yes, but the gain is smaller. What mattered most in my case was having four machines described in one place, knowing which commit runs on each and letting an agent session integrate something new with a commit and a deploy. With one machine you still get rollback and a reproducible configuration, which is already a lot.

How is everything monitored?

With a module of the flake that runs on every machine and serves a dashboard for the tablet: load, memory, GPU, who uses the GPU, power in watts, services, containers and which commit is deployed. If a machine runs another commit or needs a reboot, the dashboard flags it.

NEWSLETTER · MEASURED

A new measurement, when there is one.

Each post with its numbers, its failures and the scripts to repeat it. One to three emails a month. Unsubscribe with one click.

LATEST

I run Listmonk on my own server; your address stays there. You confirm by email before getting anything.

Comments

No comments yet. The first one is yours.

Reviewed before publishing. The email is not stored and never appears anywhere.