<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
    <title>Ray&#39;s Journal</title>
    <subtitle>Thoughts and notes on software, technology, and everyday curiosities.</subtitle>
    <link rel="self" type="application/atom+xml" href="https://journal.rayzhang.top/atom.xml"/>
    <link rel="alternate" type="text/html" href="https://journal.rayzhang.top"/>
    <generator uri="https://www.getzola.org/">Zola</generator>
    <updated>2026-03-13T00:00:00+00:00</updated>
    <id>https://journal.rayzhang.top/atom.xml</id>
    <entry xml:lang="en">
        <title>From Chaos to Control: Rebuilding My Infrastructure</title>
        <published>2026-03-13T00:00:00+00:00</published>
        <updated>2026-03-13T00:00:00+00:00</updated>
        
        <author>
          <name>Unknown</name>
        </author>
        
        <link rel="alternate" type="text/html" href="https://journal.rayzhang.top/posts/from-chaos-to-control-rebuilding-my-infrastructure/"/>
        <id>https://journal.rayzhang.top/posts/from-chaos-to-control-rebuilding-my-infrastructure/</id>
        
        <content type="html" xml:base="https://journal.rayzhang.top/posts/from-chaos-to-control-rebuilding-my-infrastructure/">&lt;h2 id=&quot;0x00-introduction&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#0x00-introduction&quot; aria-label=&quot;Anchor link for: 0x00-introduction&quot;&gt;[0x00] Introduction&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I’ve always believed that everyone deserves a second chance, especially when things were poorly designed from the start and inevitably went sideways later on.&lt;/p&gt;
&lt;p&gt;So I’m finally back. It has been a few months since I took down my original blog site, &lt;code&gt;blog.rayzhang.top&lt;/code&gt;&lt;sup class=&quot;footnote-reference&quot; id=&quot;fr-1-1&quot;&gt;&lt;a href=&quot;https://journal.rayzhang.top/posts/from-chaos-to-control-rebuilding-my-infrastructure/#fn-1&quot;&gt;[1]&lt;/a&gt;&lt;/sup&gt;, along with several other services. I made the decision to rebuild everything around Nix/NixOS. It was a rough ride, but meaningful and necessary in the end.&lt;/p&gt;
&lt;div class=&quot;note-container&quot;&gt;
&lt;div class=&quot;note-header&quot;&gt;
&lt;div class=&quot;note-icon&quot;&gt;
&lt;p&gt;TL;DR&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class=&quot;note-content&quot;&gt;
&lt;p&gt;This site is built with &lt;a rel=&quot;external&quot; href=&quot;https://www.getzola.org/&quot;&gt;Zola&lt;/a&gt;, with the source available &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Journal&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;All server configurations are defined using &lt;a rel=&quot;external&quot; href=&quot;https://nixos.org/&quot;&gt;NixOS&lt;/a&gt;, with the repository available &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Polaris&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Secrets and tokens are managed through &lt;a rel=&quot;external&quot; href=&quot;https://github.com/Mic92/sops-nix&quot;&gt;sops-nix&lt;/a&gt;, which builds on &lt;a rel=&quot;external&quot; href=&quot;https://github.com/mozilla/sops&quot;&gt;sops&lt;/a&gt;. The related configuration can be found &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Prism&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;0x01-the-early-chaos&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#0x01-the-early-chaos&quot; aria-label=&quot;Anchor link for: 0x01-the-early-chaos&quot;&gt;[0x01] The Early Chaos&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When I first started deploying my services, I had just enough Linux experience to be dangerous. Things worked—but mostly by accident.&lt;/p&gt;
&lt;p&gt;Over the past five years, my setup went through several phases:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Stage I. Everything on One Box&lt;/p&gt;
&lt;p&gt;A single CentOS VPS, binaries pulled from GitHub, and handwritten systemd units. Certificates were renewed manually. The blog was built locally and uploaded over SSH.&lt;/p&gt;
&lt;p&gt;It worked—but only because I didn’t know better.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stage II. Arch BTW&lt;/p&gt;
&lt;p&gt;After a few painful VPS migrations, I switched to Arch for its package ecosystem. It was flexible, powerful, and surprisingly usable in production—at least for a “toy project.”&lt;/p&gt;
&lt;p&gt;This was also when I moved to ACME for automated certificates.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stage III. Functionality First&lt;/p&gt;
&lt;p&gt;With more resources, I started treating my setup like a real system.&lt;/p&gt;
&lt;p&gt;I spread services across multiple VPS providers and built a private network using Tailscale (with Headscale). Services became modular—gateways, backends, databases—all running on different nodes.&lt;/p&gt;
&lt;p&gt;It looked more “professional,” but complexity started to grow faster than I could manage.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stage IV. Hitting the Wall&lt;/p&gt;
&lt;p&gt;Eventually, the cracks became obvious.&lt;/p&gt;
&lt;p&gt;Even with better tooling, I still couldn’t reliably migrate or rebuild my infrastructure. Too many moving parts—configs, data, and implicit state scattered everywhere.&lt;/p&gt;
&lt;p&gt;Dotfiles weren’t enough. Backups weren’t enough.&lt;/p&gt;
&lt;p&gt;That’s when I started seriously looking at Nix.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;0x02-why-nix-now&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#0x02-why-nix-now&quot; aria-label=&quot;Anchor link for: 0x02-why-nix-now&quot;&gt;[0x02] Why Nix Now?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I didn’t go looking for Nix. I got cornered into it.&lt;/p&gt;
&lt;p&gt;By the time I reached that point, the problem was no longer about package managers or distributions. It was about control. I couldn’t reliably rebuild my own infrastructure.&lt;/p&gt;
&lt;p&gt;Every machine had its own state. Services depended on configurations that lived half in Git, half on disk, and half in my memory. Migrations were slow, fragile, and stressful. Even when I thought I had everything backed up, there was always something missing.&lt;/p&gt;
&lt;p&gt;What I wanted was simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;A way to define the entire system in one place&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A way to reproduce it on any machine&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A way to make changes without breaking everything&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything I had tried before only solved part of the problem.&lt;/p&gt;
&lt;p&gt;Nix was the first thing that addressed it as a whole.&lt;/p&gt;
&lt;p&gt;Instead of treating packages, services, and system configuration as separate layers, Nix describes them together—using the same language, in the same place. The system becomes something you declare, rather than something you gradually mutate over time. That shift changes how you think about infrastructure.&lt;/p&gt;
&lt;p&gt;Rebuilding a machine is no longer a special operation—it’s just applying the same configuration somewhere else. Switching between stable and bleeding-edge components is no longer a risky upgrade—it’s just choosing a different input.&lt;/p&gt;
&lt;p&gt;Of course, this comes with a cost. Nix introduces its own complexity, and understanding it takes time. At a glance, it can feel like just another abstraction layer—something we’ve all learned to be cautious about. But this time, the trade-off made sense.&lt;/p&gt;
&lt;p&gt;For the first time, I felt like I could describe my system completely, version it, and rebuild it when needed. Not partially. Not approximately. But consistently. That was enough reason to start over.&lt;/p&gt;
&lt;h2 id=&quot;0x03-design-philosophy&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#0x03-design-philosophy&quot; aria-label=&quot;Anchor link for: 0x03-design-philosophy&quot;&gt;[0x03] Design Philosophy&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I had accumulated many ideas long before I decided to nuke everything and start from scratch. Without clear design goals, the system would inevitably drift into something messy and difficult to maintain.&lt;/p&gt;
&lt;p&gt;So before rebuilding anything, it was necessary to step back and rethink what I actually wanted this infrastructure to achieve.&lt;/p&gt;
&lt;h3 id=&quot;network-architecture&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#network-architecture&quot; aria-label=&quot;Anchor link for: network-architecture&quot;&gt;Network Architecture&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In China, ISPs are usually very strict about inbound traffic. In many cases, residential networks do not allow users to open ports for their own services. Even if you manage to obtain a dedicated public IP and port, the availability of that service is not always guaranteed.&lt;/p&gt;
&lt;p&gt;This creates a practical challenge for self-hosting. Many of my services require significant CPU, memory, and storage, which makes them better suited to run on dedicated hardware at home. However, exposing those services directly to the public internet is often difficult under these network restrictions.&lt;/p&gt;
&lt;p&gt;As a result, many people rely on VPS servers as the public entry point for their infrastructure. But VPS instances are typically limited in resources unless you are willing to pay a significant amount of money.&lt;/p&gt;
&lt;p&gt;To address this problem, I decided to build my infrastructure around a VPN-based network and expose services through a layered gateway design.&lt;/p&gt;
&lt;p&gt;The basic rules of this architecture are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;As many devices as possible are included in my Tailscale VPN network.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;VPS servers act as L1 gateways using Nginx. They receive traffic from the public internet and forward it to internal nodes through reverse proxy.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;L2 gateways, also powered by Nginx, run on the actual service hosts (usually dedicated hardware at home) and route traffic to the corresponding services.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Inside the Tailscale network, any authenticated client can access services through the L2 gateways. This allows both public and private services to be reachable within the VPN.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;From the public internet, access is strictly controlled at the L1 gateway. Only explicitly configured domains for public services are allowed to pass through.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;script src=https://journal.rayzhang.top/js/mermaid.js&gt;&lt;/script&gt;
&lt;pre class=&quot;mermaid&quot;&gt;

flowchart LR
%% Internet
USER[Internet Users]

%% ================= VPN =================
subgraph TS[&quot;Tailscale VPN Network&quot;]
    direction LR

    %% Clients
    subgraph CLIENTS[&quot;Clients&quot;]
        LAPTOP[Laptop]
        PHONE[Phone]
    end

    %% L1 Gateways
    subgraph VPS[&quot;VPS Edge Nodes&quot;]
        L1A[&quot;L1 Gateway&lt;br/&gt;Nginx Reverse Proxy&quot;]
        L1B[&quot;L1 Gateway&lt;br/&gt;Nginx Reverse Proxy&quot;]
    end

    %% ================= Service Nodes =================
    subgraph SERVICES[&quot;Home Service Nodes&quot;]
        subgraph RPI[&quot;Raspberry Pi&quot;]
            L2R[&quot;L2 Gateway&lt;br/&gt;Nginx&quot;]
            subgraph PUBLIC_RPI[&quot;🌐 Public Internet-Exposed&quot;]
                BLOG[&quot;Static Blog&quot;]
            end
        end

        subgraph ROCK[&quot;ROCK5B+&quot;]
            L2K[&quot;L2 Gateway&lt;br/&gt;Nginx&quot;]
            subgraph PUBLIC_ROCK[&quot;🌐 Public Internet-Exposed&quot;]
                FORGEJO[&quot;Forgejo&quot;]
                VAULT[&quot;Vaultwarden&quot;]
                S3[&quot;Garage S3&quot;]
            end
        end

        subgraph MINI[&quot;Mini PC&quot;]
            L2M[&quot;L2 Gateway&lt;br/&gt;Nginx&quot;]
            subgraph PRIVATE_MINI[&quot;🔒 Private VPN Only&quot;]
                WIKI[&quot;Internal Wiki&quot;]
                HOME[&quot;Smart Home&quot;]
            end
        end

        %% -------- Data Layer inside Home --------
        subgraph DATA[&quot;🗄️ Data Layer Internal&quot;]
            PG[(&quot;PostgreSQL&quot;)]
        end
    end
end

%% ================= FLOWS =================
USER--&gt;|HTTPS|L1A
USER--&gt;|HTTPS|L1B

L1A--&gt;|VPN|L2R
L1A--&gt;|VPN|L2K
L1B--&gt;|VPN|L2K

LAPTOP--&gt;|VPN|L2M
PHONE--&gt;|VPN|L2M
LAPTOP--&gt;|VPN|L2K

L2R--&gt;BLOG
L2K--&gt;FORGEJO
L2K--&gt;VAULT
L2K--&gt;S3
L2M--&gt;WIKI
L2M--&gt;HOME

FORGEJO--&gt;|VPN|PG
VAULT--&gt;|VPN|PG
WIKI--&gt;|VPN|PG

%% ================= STYLES =================
classDef l1 fill:#ff9f43,stroke:#ffffff,stroke-width:2px,color:#000
classDef l2 fill:#feca57,stroke:#ffffff,stroke-width:2px,color:#000
classDef public fill:#2ecc71,stroke:#ffffff,stroke-width:2px,color:#000
classDef private fill:#3498db,stroke:#ffffff,stroke-width:2px,color:#fff
classDef data fill:#9b59b6,stroke:#ffffff,stroke-width:2px,color:#fff
classDef internet fill:#e74c3c,stroke:#ffffff,stroke-width:2px,color:#fff

class L1A,L1B l1
class L2R,L2K,L2M l2
class BLOG,FORGEJO,VAULT,S3 public
class WIKI,HOME private
class PG data
class USER internet

style PUBLIC_RPI fill:transparent,stroke:#2ecc71,stroke-width:2px,stroke-dasharray: 5 5
style PUBLIC_ROCK fill:transparent,stroke:#2ecc71,stroke-width:2px,stroke-dasharray: 5 5
style PRIVATE_MINI fill:transparent,stroke:#3498db,stroke-width:2px,stroke-dasharray: 5 5

&lt;/pre&gt;
&lt;h3 id=&quot;domain-naming-strategy&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#domain-naming-strategy&quot; aria-label=&quot;Anchor link for: domain-naming-strategy&quot;&gt;Domain Naming Strategy&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I bought my first domain, &lt;code&gt;rayzhang.top&lt;/code&gt;, back in 2021. It was enough when everything lived in one place. That changed once I started splitting services across different environments.&lt;/p&gt;
&lt;p&gt;Some services were meant to be public. Others were strictly internal, accessible only through my Tailscale network. Mixing them under the same domain quickly became confusing—not just in naming, but also in access control and routing.&lt;/p&gt;
&lt;p&gt;There was also a more practical concern. Since most of my infrastructure is deployed outside China, the legal status of certain deployments isn’t always clear. If I ever want to host services within mainland China legally, I would need a separate domain with ICP registration.&lt;/p&gt;
&lt;p&gt;At that point, I stopped treating domain names as simple labels and started treating them as boundaries—between public and private, internal and external, global and mainland.&lt;/p&gt;
&lt;p&gt;To make this explicit, I defined a set of naming conventions:&lt;/p&gt;
&lt;table&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Domain Rules&lt;/th&gt;&lt;th&gt;DNS&lt;/th&gt;&lt;th&gt;Purpose&lt;/th&gt;&lt;th&gt;Notes&lt;/th&gt;&lt;th&gt;Examples&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;node-${location}.rayzhang.top&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Public&lt;/td&gt;&lt;td&gt;Resolve a server by its location.&lt;/td&gt;&lt;td&gt;Not intended for direct service access. Typically implemented as CNAME records pointing to actual hosts.&lt;/td&gt;&lt;td&gt;&lt;code&gt;node-lax.rayzhang.top&lt;/code&gt;&lt;br/&gt;&lt;code&gt;node-hel.rayzhang.top&lt;/code&gt;&lt;br/&gt;&lt;code&gt;node-lhr.rayzhang.top&lt;/code&gt;&lt;br/&gt;&lt;code&gt;node-ngb.rayzhang.top&lt;/code&gt;&lt;br/&gt;&lt;code&gt;node-intl.rayzhang.top&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;${deviceName}.netscale.cc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Tailscale&lt;/td&gt;&lt;td&gt;Resolve a machine by its device name.&lt;/td&gt;&lt;td&gt;Part of Tailscale MagicDNS.&lt;/td&gt;&lt;td&gt;&lt;code&gt;mjolnir.netscale.cc&lt;/code&gt;&lt;br/&gt;&lt;code&gt;brokk.netscale.cc&lt;/code&gt;&lt;br/&gt;&lt;code&gt;heimdall.netscale.cc&lt;/code&gt;&lt;br/&gt;&lt;code&gt;odin.netscale.cc&lt;/code&gt;&lt;br/&gt; &lt;code&gt;forseti.netscale.cc&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;${serviceName}.netscale.cc&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Tailscale&lt;/td&gt;&lt;td&gt;Resolve private services by name.&lt;/td&gt;&lt;td&gt;Accessible only within the VPN (L2 gateway).&lt;/td&gt;&lt;td&gt;&lt;code&gt;nexus.netscale.cc&lt;/code&gt;&lt;br/&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;${serviceName}.rayzhang.top&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Public&lt;/td&gt;&lt;td&gt;Resolve public services by name.&lt;/td&gt;&lt;td&gt;Exposed through L1 gateway.&lt;/td&gt;&lt;td&gt;&lt;code&gt;flux.rayzhang.top&lt;/code&gt;&lt;br/&gt;&lt;code&gt;keystone.rayzhang.top&lt;/code&gt;&lt;br/&gt;&lt;code&gt;journal.rayzhang.top&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;${serviceName}.netscale.work&lt;/code&gt;&lt;/td&gt;&lt;td&gt;Public&lt;/td&gt;&lt;td&gt;Public services hosted within mainland China.&lt;/td&gt;&lt;td&gt;Requires ICP registration. Used globally for consistency when a service is defined under this domain.&lt;/td&gt;&lt;td&gt;&lt;code&gt;derper-bifrost.netscale.work&lt;/code&gt;&lt;br/&gt;&lt;code&gt;derper-heimdall.netscale.work&lt;/code&gt;&lt;br/&gt;&lt;code&gt;derper-forseti.netscale.work&lt;/code&gt;&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;h2 id=&quot;0x04-implementation-making-it-real&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#0x04-implementation-making-it-real&quot; aria-label=&quot;Anchor link for: 0x04-implementation-making-it-real&quot;&gt;[0x04] Implementation: Making It Real&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point, the architecture and naming conventions were clear. The remaining question was how to actually implement them in a way that wouldn’t fall apart again in six months.&lt;/p&gt;
&lt;p&gt;This is where Nix started to matter—not as a tool I wanted to use, but as a tool that could enforce the structure I had already designed.&lt;/p&gt;
&lt;h3 id=&quot;system-as-a-single-source-of-truth&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#system-as-a-single-source-of-truth&quot; aria-label=&quot;Anchor link for: system-as-a-single-source-of-truth&quot;&gt;System as a Single Source of Truth&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In the past, my infrastructure existed in fragments. Some configurations were in Git. Others lived directly on servers. A few only existed in my memory. Rebuilding a machine meant retracing steps, hoping nothing was forgotten.&lt;/p&gt;
&lt;p&gt;With Nix, I started treating the entire system as a single, versioned definition.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every machine is declared.&lt;/li&gt;
&lt;li&gt;Every service is defined.&lt;/li&gt;
&lt;li&gt;Every dependency is pinned.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Adding a new node is no longer a manual process. It’s just another entry in the configuration. Rebuilding a server doesn’t feel like recovery anymore—it feels like routine.&lt;/p&gt;
&lt;p&gt;At the same time, I started organizing my machines more intentionally. Each device has its own configuration file, and I name them using Norse mythology—partly for fun but also to make them easier to recognize at a glance. For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Raspberry Pi 3B+ &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Polaris/src/branch/main/hosts/aarch64-linux/brokk/default.nix&quot;&gt;&lt;strong&gt;BROKK&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Rock 5B+ &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Polaris/src/branch/main/hosts/aarch64-linux/mjolnir/default.nix&quot;&gt;&lt;strong&gt;MJOLNIR&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;DMIT LAX VPS &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Polaris/src/branch/main/hosts/x86_64-linux/heimdall/default.nix&quot;&gt;&lt;strong&gt;HEIMDALL&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Each of these configurations is not written from scratch. Instead, they are composed of reusable modules. Services are defined once, as independent building blocks, and then imported where needed—almost like assembling LEGO pieces.&lt;/p&gt;
&lt;p&gt;A typical configuration looks like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; self&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span&gt; lib&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span&gt; system&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span&gt; sops&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span&gt; prism&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span&gt; deploy-rs&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span&gt; journal&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; ...&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;let&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;  deviceName&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;brokk&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;  modules&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt; lib&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;scanPath&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;      path&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; ./.&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;      recursive&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;    }&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; ++&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; map&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt; lib&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;relativeToRoot&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;modules/nixos/base&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;modules/nixos/server/shairport-sync.nix&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;modules/nixos/network/wifi.nix&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;modules/nixos/server/acme-pusher.nix&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;    &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;modules/nixos/server/zola-blog.nix&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  ]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;  nixosConfigurations&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;deviceName&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt; lib&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;nixosSystem&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;    inherit&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; system&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; modules&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    specialArgs&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; inherit&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; lib&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; sops&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; prism&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; journal&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; deviceName&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-5&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-5&quot;&gt; .....&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This approach keeps the system consistent while still allowing each machine to have its own role.&lt;/p&gt;
&lt;p&gt;Instead of configuring servers individually, I’m assembling them from shared components. That makes the overall structure easier to understand—and much easier to change.&lt;/p&gt;
&lt;p&gt;Even more, each node in my infrastructure is just a Nix configuration. Adding a new server is no longer “setting up a machine”—it’s adding a file.&lt;/p&gt;
&lt;h3 id=&quot;secrets-management&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#secrets-management&quot; aria-label=&quot;Anchor link for: secrets-management&quot;&gt;Secrets Management&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;One thing that always broke the illusion of a “fully declarative system” was secrets.&lt;/p&gt;
&lt;p&gt;You can define packages. You can define services. You can even define entire machines. But you can’t just commit passwords, API tokens, or private keys into your repository.&lt;/p&gt;
&lt;p&gt;In my earlier setups, secrets were always handled separately—and usually manually. I would copy them over, store them in random files, or rely on memory. It worked, but it also meant the system was never truly reproducible. There was always a missing piece.&lt;/p&gt;
&lt;p&gt;With Nix, I wanted to avoid that gap. The system should be fully defined but still safe to store in a public or shared repository. To achieve this, I integrated encrypted secrets using sops-nix. Secrets are stored alongside the configuration but encrypted in a separate repository &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Prism&quot;&gt;&lt;strong&gt;Prism&lt;/strong&gt;&lt;/a&gt;. Only the target machines can decrypt them at deployment time using their own keys.&lt;/p&gt;
&lt;p&gt;This setup also allows me to handle more than just simple credentials. For example, I use it to distribute my acme.sh certificates.&lt;/p&gt;
&lt;p&gt;Certificates are encrypted and pushed to the repository via an &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Polaris/src/branch/main/modules/nixos/server/acme-pusher.nix&quot;&gt;&lt;strong&gt;automatic cert sync bot service&lt;/strong&gt;&lt;/a&gt;. The encrypted files (ending in &lt;code&gt;.enc&lt;/code&gt;) can be found &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Prism/src/branch/main/certs&quot;&gt;&lt;strong&gt;here&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With sops-nix, decrypting and using secrets becomes part of the system configuration itself. For example, my &lt;a rel=&quot;external&quot; href=&quot;https://flux.rayzhang.top/rayzhang1378/Polaris/src/branch/main/modules/nixos/server/vaultwarden.nix&quot;&gt;&lt;strong&gt;Vaultwarden config&lt;/strong&gt;&lt;/a&gt; looks like this:&lt;/p&gt;
&lt;pre class=&quot;giallo z-l-code z-d-code&quot; &gt;&lt;code data-lang=&quot;nix&quot;&gt;&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;span&gt; prism&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span&gt; deviceName&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; ...&lt;/span&gt;&lt;span&gt; }&lt;/span&gt;&lt;span&gt;:&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;let&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;  domain&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;keystone.rayzhang.top&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;in&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-5&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-5&quot;&gt; .....&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;  sops&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;secrets&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;certs/&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;/fullchain.pem&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    sopsFile&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;prism&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;/certs/rayzhang.top/fullchain.pem.enc&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    owner&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;nginx&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    group&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;nginx&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    mode&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;0640&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    format&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    restartUnits&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;nginx.service&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;  sops&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;secrets&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;certs/&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;/key.pem&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    sopsFile&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;prism&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;/certs/rayzhang.top/key.pem.enc&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    owner&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;nginx&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    group&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;nginx&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    mode&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;0640&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    format&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;binary&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    restartUnits&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span&gt; [&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt; &amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;nginx.service&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span&gt; ]&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  &lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-5&quot;&gt;  #&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-5&quot;&gt;.....&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;  services&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;nginx&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;virtualHosts&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span&gt; {&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    forceSSL&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    sslCertificate&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;sops&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;certs/&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;/fullchain.pem&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;path&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    sslCertificateKey&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt; config&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;sops&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;secrets&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;certs/&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;${&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;domain&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;/key.pem&lt;/span&gt;&lt;span class=&quot;z-l-3 z-d-4&quot;&gt;&amp;quot;&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;z-l-8 z-d-9&quot;&gt;path&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    quic&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    http3&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt;    kTLS&lt;/span&gt;&lt;span class=&quot;z-l-9 z-d-7&quot;&gt; =&lt;/span&gt;&lt;span class=&quot;z-l-1 z-d-3&quot;&gt; true&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span class=&quot;z-l-6 z-d-5&quot;&gt;    #&lt;/span&gt;&lt;span class=&quot;z-l-6 z-d-5&quot;&gt; .....&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;  }&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;
&lt;span class=&quot;giallo-l&quot;&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This changes the workflow in a subtle but important way. Secrets are no longer external artifacts that need to be managed separately. They become part of the system definition—just in a protected form.&lt;/p&gt;
&lt;p&gt;From the outside, nothing changes. Services still receive the same environment variables. Applications still read the same configuration files. But internally, everything is now consistent.&lt;/p&gt;
&lt;h2 id=&quot;0x05-trade-offs-and-reality&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#0x05-trade-offs-and-reality&quot; aria-label=&quot;Anchor link for: 0x05-trade-offs-and-reality&quot;&gt;[0x05] Trade-offs and Reality&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This approach is not free. Nix solves many of the problems I had before—but it also introduces a different kind of complexity.&lt;/p&gt;
&lt;p&gt;The learning curve is real. Understanding how everything fits together takes time, and sometimes the error messages don’t make things easier. When something breaks, debugging can feel like peeling layers of abstraction, trying to figure out what actually went wrong underneath.&lt;/p&gt;
&lt;p&gt;One area where this becomes especially noticeable is &lt;strong&gt;state management&lt;/strong&gt;. Applications are not purely declarative. They generate state at runtime—databases, cache files, user data, and various artifacts in the home directory. These files are not part of the system definition, yet they shape how the system behaves at runtime.&lt;/p&gt;
&lt;p&gt;That makes them a weak point—an upgrade or rollback can easily introduce subtle inconsistencies, especially when the application’s expectations no longer align with the existing state.&lt;/p&gt;
&lt;p&gt;This creates a gap. You can fully define how a service is deployed, but not always how its data evolves over time. NixOS tries to address this with mechanisms like &lt;code&gt;stateVersion&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At a high level, stateVersion acts as a reference point for how certain system components should behave, especially when defaults or data formats change over time. It helps maintain compatibility across upgrades.&lt;/p&gt;
&lt;p&gt;But it also reveals something important: even in NixOS, the system cannot fully escape its past. Compatibility has to be preserved somewhere. In practice, this means there is always a boundary between: what Nix can define and what must be managed as a state. And that boundary doesn’t disappear just because the system is declarative.&lt;/p&gt;
&lt;p&gt;I’ve found that trying to force everything into a stateless model doesn’t always work. Some parts of the system—databases, user data, application state—are inherently persistent. They need to be treated differently, with backups, migrations, and careful handling during upgrades.&lt;/p&gt;
&lt;p&gt;Nix doesn’t eliminate that responsibility. It just makes the boundary more visible. So while the system feels declarative, it’s not entirely stateless. And that’s okay.&lt;/p&gt;
&lt;h2 id=&quot;references&quot;&gt;&lt;a class=&quot;zola-anchor&quot; href=&quot;#references&quot; aria-label=&quot;Anchor link for: references&quot;&gt;References&lt;/a&gt;&lt;/h2&gt;
&lt;section class=&quot;footnotes&quot;&gt;
&lt;ol class=&quot;footnotes-list&quot;&gt;
&lt;li id=&quot;fn-1&quot;&gt;
&lt;p&gt;&lt;code&gt;blog.rayzhang.top&lt;/code&gt; was my first attempt to build a static blog using &lt;a rel=&quot;external&quot; href=&quot;https://hexo.io/&quot;&gt;Hexo&lt;/a&gt; &lt;a rel=&quot;external&quot; href=&quot;https://github.com/jerryc127/hexo-theme-butterfly&quot;&gt;Butterfly&lt;/a&gt;. It was written in zh-Hans. &lt;a href=&quot;https://journal.rayzhang.top/posts/from-chaos-to-control-rebuilding-my-infrastructure/#fr-1-1&quot;&gt;↩&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/section&gt;
</content>
        
    </entry>
</feed>
