<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Parker Jones Dev Blog - flakes</title>
    <subtitle>Dev Blog of Parker Jones</subtitle>
    <link rel="self" type="application/atom+xml" href="https://parkerjones.dev/tags/flakes/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://parkerjones.dev"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-06-26T00:00:00+00:00</updated>
    <id>https://parkerjones.dev/tags/flakes/atom.xml</id>
    <entry xml:lang="en">
        <title>One Flake, Every Machine: a NixOS + macOS Fleet</title>
        <published>2026-06-26T00:00:00+00:00</published>
        <updated>2026-06-26T00:00:00+00:00</updated>
        
        <author>
          <name>
            
              Unknown
            
          </name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://parkerjones.dev/posts/nix-fleet-one-flake/"/>
        <id>https://parkerjones.dev/posts/nix-fleet-one-flake/</id>
        
        <content type="html" xml:base="https://parkerjones.dev/posts/nix-fleet-one-flake/">&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nix Fleet, part 1.&lt;&#x2F;strong&gt; This post is the architecture overview. Later parts go deep on the pieces it depends on: cross-arch remote builders, fleet-wide secrets with agenix, and bare-metal install of a NAS with disko.&lt;&#x2F;p&gt;
&lt;&#x2F;blockquote&gt;
&lt;p&gt;Every machine I work on — Linux servers, a NAS, and my M3 MacBook — is configured by &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;parallaxisjones&#x2F;dotfiles&quot;&gt;one Git repository&lt;&#x2F;a&gt;. One &lt;code&gt;flake.nix&lt;&#x2F;code&gt; describes all of them: same shell, same tools, same agent skills, same secrets handling, whether the target is &lt;code&gt;x86_64-linux&lt;&#x2F;code&gt;, &lt;code&gt;aarch64-linux&lt;&#x2F;code&gt;, or &lt;code&gt;aarch64-darwin&lt;&#x2F;code&gt;. Adding a machine means adding a host entry; changing my setup everywhere means one commit and a rebuild.&lt;&#x2F;p&gt;
&lt;p&gt;This is the &quot;single source of truth for my computing environment&quot; that Nix promises and rarely delivers without a fight. Here&#x27;s how the repo is laid out and the one trick that makes cross-architecture management actually pleasant.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-shape-of-the-repo&quot;&gt;The shape of the repo&lt;&#x2F;h2&gt;
&lt;pre&gt;&lt;code data-lang=&quot;text&quot;&gt;nixos-config&#x2F;
├── flake.nix          # inputs + outputs: hosts, devShells, apps
├── hosts&#x2F;
│   ├── nixos&#x2F;         # configuration.nix, hardware-configuration.nix,
│   │                  # helios64.nix (the NAS), profiles&#x2F;
│   └── darwin&#x2F;        # configuration.nix (the Mac)
├── modules&#x2F;
│   ├── shared&#x2F;        # config + secrets shared across OSes
│   ├── nixos&#x2F;         # homelab services, Linux-only config
│   └── darwin&#x2F;        # dock, homebrew casks, home-manager
├── overlays&#x2F;          # package customizations
└── justfile           # deploy &#x2F; check convenience targets
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;The split that makes this maintainable: &lt;strong&gt;&lt;code&gt;hosts&#x2F;&lt;&#x2F;code&gt; is per-machine, &lt;code&gt;modules&#x2F;&lt;&#x2F;code&gt; is shared.&lt;&#x2F;strong&gt; A host file is mostly a list of which modules to import plus its hardware specifics. The actual configuration — my packages, dotfiles, services — lives in modules that any host can pull in. When I want a tool on every machine, it goes in &lt;code&gt;modules&#x2F;shared&lt;&#x2F;code&gt;; when it&#x27;s Linux-only, &lt;code&gt;modules&#x2F;nixos&lt;&#x2F;code&gt;; macOS-only, &lt;code&gt;modules&#x2F;darwin&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;what-s-wired-together&quot;&gt;What&#x27;s wired together&lt;&#x2F;h2&gt;
&lt;p&gt;The flake stitches together the whole modern Nix ecosystem:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;nix&quot;&gt;inputs = {
  nixpkgs.url = &amp;quot;github:nixos&#x2F;nixpkgs&#x2F;nixos-unstable&amp;quot;;
  home-manager.url = &amp;quot;github:nix-community&#x2F;home-manager&amp;quot;;
  darwin.url = &amp;quot;github:LnL7&#x2F;nix-darwin&#x2F;master&amp;quot;;   # macOS, declaratively
  nix-homebrew.url = &amp;quot;github:zhaofengli-wip&#x2F;nix-homebrew&amp;quot;;
  disko.url = &amp;quot;github:nix-community&#x2F;disko&amp;quot;;        # declarative partitioning
  fenix.url = &amp;quot;github:nix-community&#x2F;fenix&amp;quot;;         # Rust toolchains
  agenix.url = &amp;quot;github:ryantm&#x2F;agenix&amp;quot;;              # age-encrypted secrets
  secrets.url = &amp;quot;git+ssh:&#x2F;&#x2F;git@github.com&#x2F;parallaxisjones&#x2F;nix-secrets.git&amp;quot;;
  my-skills.url = &amp;quot;github:parallaxisjones&#x2F;skills&amp;quot;;  # my Claude skills
  # ...
};
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;A few deliberate choices in there:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;nix-darwin&lt;&#x2F;code&gt; + &lt;code&gt;home-manager&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; mean my Mac is configured by the same mechanisms as my Linux boxes. The dock, defaults, and dotfiles are all declarative.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;nix-homebrew&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; is the pragmatic concession: GUI Mac apps still come from Homebrew casks, but managed &lt;em&gt;through&lt;&#x2F;em&gt; Nix so the cask list is in the repo, not in my shell history.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;fenix&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; pins my Rust toolchain so &lt;code&gt;cargo&lt;&#x2F;code&gt;&#x2F;&lt;code&gt;clippy&lt;&#x2F;code&gt; are identical everywhere.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;secrets&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; is a &lt;em&gt;separate private repo&lt;&#x2F;em&gt; pulled in as an input — encrypted secrets are versioned and shared across hosts without living in this (public) config. That&#x27;s its own post.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;my-skills&lt;&#x2F;code&gt;&lt;&#x2F;strong&gt; wires my &lt;a href=&quot;&#x2F;posts&#x2F;skills-with-nix&#x2F;&quot;&gt;Claude agent skills&lt;&#x2F;a&gt; into the same declarative system.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;h2 id=&quot;the-cross-arch-trick-build-where-you-can&quot;&gt;The cross-arch trick: build where you can&lt;&#x2F;h2&gt;
&lt;p&gt;Here&#x27;s the problem a mixed fleet runs into immediately: &lt;strong&gt;my macOS laptop cannot build Linux derivations locally.&lt;&#x2F;strong&gt; So how do I deploy to the NixOS box from the Mac? The answer is to build &lt;em&gt;on the target&lt;&#x2F;em&gt; and just orchestrate from the laptop. My &lt;code&gt;justfile&lt;&#x2F;code&gt; deploy recipe:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;make&quot;&gt;# Build + switch the NixOS host remotely over SSH. Builds ON the server
# (--build-host) since a darwin laptop can&amp;#39;t build Linux derivations locally.
deploy host=nixos_host:
    nix run nixpkgs#nixos-rebuild -- switch \
      --flake .#{{nixos_attr}} \
      --target-host parallaxis@{{host}} \
      --build-host  parallaxis@{{host}} \
      --use-remote-sudo
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;&lt;code&gt;--build-host&lt;&#x2F;code&gt; and &lt;code&gt;--target-host&lt;&#x2F;code&gt; both pointing at the server means the laptop never compiles a Linux package — it hands the flake to the server, the server builds and switches, and &lt;code&gt;--use-remote-sudo&lt;&#x2F;code&gt; handles the privilege step. I get to drive everything from my editor on macOS. (Part 2 covers the inverse — building Linux artifacts &lt;em&gt;from&lt;&#x2F;em&gt; the Mac via a remote builder — for when you do want local orchestration of the build itself.)&lt;&#x2F;p&gt;
&lt;p&gt;There&#x27;s a &lt;code&gt;check&lt;&#x2F;code&gt; target too, for a fast feedback loop that doesn&#x27;t build anything:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;make&quot;&gt;check:
    nix eval .#nixosConfigurations.{{nixos_attr}}.config.system.build.toplevel.drvPath
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;Evaluating the derivation path is a near-instant syntax-and-type check of the whole host config — the Nix equivalent of &lt;code&gt;tsc --noEmit&lt;&#x2F;code&gt;.&lt;&#x2F;p&gt;
&lt;h2 id=&quot;the-gotcha-that-cost-me-time&quot;&gt;The gotcha that cost me time&lt;&#x2F;h2&gt;
&lt;p&gt;My &lt;code&gt;nixosConfigurations&lt;&#x2F;code&gt; are keyed by &lt;strong&gt;system string&lt;&#x2F;strong&gt;, not hostname. So the deploy target is &lt;code&gt;.#x86_64-linux&lt;&#x2F;code&gt;, &lt;em&gt;not&lt;&#x2F;em&gt; &lt;code&gt;.#nixos&lt;&#x2F;code&gt;:&lt;&#x2F;p&gt;
&lt;pre&gt;&lt;code data-lang=&quot;make&quot;&gt;nixos_attr := &amp;quot;x86_64-linux&amp;quot;   # NOT the hostname
&lt;&#x2F;code&gt;&lt;&#x2F;pre&gt;
&lt;p&gt;If you key your configs this way and then try &lt;code&gt;nixos-rebuild --flake .#nixos&lt;&#x2F;code&gt;, you get a confusing &quot;attribute not found&quot; with no hint about why. Worth a comment in your own repo so future-you doesn&#x27;t lose twenty minutes to it. (I did.)&lt;&#x2F;p&gt;
&lt;h2 id=&quot;why-it-s-worth-the-upfront-cost&quot;&gt;Why it&#x27;s worth the upfront cost&lt;&#x2F;h2&gt;
&lt;p&gt;Nix has a real learning tax, and a single-machine setup rarely justifies it. A &lt;em&gt;fleet&lt;&#x2F;em&gt; does. The payoffs that keep me here:&lt;&#x2F;p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Small, frequent, reversible changes.&lt;&#x2F;strong&gt; Every change is a commit; every rebuild is a new generation I can roll back to atomically. I deploy config changes the way I deploy code.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;New machines reach parity from the flake.&lt;&#x2F;strong&gt; No &quot;setup day.&quot; The repo &lt;em&gt;is&lt;&#x2F;em&gt; the setup.&lt;&#x2F;li&gt;
&lt;li&gt;&lt;strong&gt;One mental model across operating systems.&lt;&#x2F;strong&gt; I stopped maintaining a separate pile of macOS hacks and a separate pile of Linux hacks. It&#x27;s all modules.&lt;&#x2F;li&gt;
&lt;&#x2F;ul&gt;
&lt;p&gt;The roadmap from here — and the next posts in this series — is the interesting infrastructure underneath: &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-remote-builders&#x2F;&quot;&gt;&lt;strong&gt;remote builders&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; so cross-compilation stops being a chore (part 2), &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-agenix-secrets&#x2F;&quot;&gt;&lt;strong&gt;agenix&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; for secrets that are versioned and fleet-wide without ever sitting in plaintext (part 3), and standing up a &lt;a href=&quot;&#x2F;posts&#x2F;nix-fleet-helios64-disko&#x2F;&quot;&gt;&lt;strong&gt;Helios64 NAS&lt;&#x2F;strong&gt;&lt;&#x2F;a&gt; from bare metal with &lt;code&gt;disko&lt;&#x2F;code&gt; (part 4). The overview is the boring part. The machinery is where Nix gets fun.&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;The full fleet configuration lives in &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;github.com&#x2F;parallaxisjones&#x2F;dotfiles&quot;&gt;&lt;code&gt;parallaxisjones&#x2F;dotfiles&lt;&#x2F;code&gt;&lt;&#x2F;a&gt;.&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
&lt;p&gt;&lt;em&gt;— Parker Jones, &lt;a rel=&quot;external&quot; href=&quot;https:&#x2F;&#x2F;parkerjones.dev&quot;&gt;parkerjones.dev&lt;&#x2F;a&gt;&lt;&#x2F;em&gt;&lt;&#x2F;p&gt;
</content>
        
    </entry>
</feed>
