<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Gitops on bismigel</title><link>https://bismigel.dev/tags/gitops/</link><description>Recent content in Gitops on bismigel</description><generator>Hugo</generator><language>en-us</language><copyright>bismigel</copyright><lastBuildDate>Mon, 12 Oct 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://bismigel.dev/tags/gitops/index.xml" rel="self" type="application/rss+xml"/><item><title>Choosing the architecture</title><link>https://bismigel.dev/posts/2026-10-12-choosing-the-architecture/</link><pubDate>Mon, 12 Oct 2026 00:00:00 +0000</pubDate><guid>https://bismigel.dev/posts/2026-10-12-choosing-the-architecture/</guid><description/><content:encoded><![CDATA[<p>I already gave a tour of the homelab. This one is about the decisions behind it, and the ones I made, unmade, and made again after living with it.</p>
<h1 id="one-control-plane-or-two">One control plane, or two</h1>
<p>The first fork was how to manage two separate single-node clusters. The obvious answer is an ArgoCD on each box. That works, but it means two control planes to babysit, two sets of repo credentials to keep in sync, and no single place to see the whole lab.</p>
<p>I went the other way: <strong>one</strong> ArgoCD, on the hosting box, managing <strong>both</strong> clusters. The media box is registered as an external cluster, so an app just says which cluster it belongs to:</p>
<div class="code-block">
  <div class="code-block-header">
    <span class="code-block-lang">yaml</span>
    <button type="button" class="code-copy" aria-label="Copy code">
      <span class="code-copy-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1"/></svg>
</span>
      <span class="code-copy-icon code-copy-icon-check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
      <span class="code-copy-label">Copy</span>
    </button>
  </div>
  <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">destination</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">name</span><span class="p">:</span><span class="w"> </span><span class="l">calamansi-intel  </span><span class="w"> </span><span class="c"># or in-cluster for the hosting box</span></span></span></code></pre></div>
</div>
<p>Everything that should run everywhere (a metrics agent, for example) is an ApplicationSet with a cluster generator, so adding a box is a matter of registering it, not copying manifests. The cost is that the control plane is a single blast radius; the win is that there is exactly one place the desired state lives, and it is git.</p>
<h1 id="where-each-thing-runs">Where each thing runs</h1>
<p>I split the apps deliberately. The media box has QuickSync, which is wasted on anything that is not media, so the whole media stack lives there. Everything else (the sites, the APIs, monitoring, the secrets machinery) lives on the hosting box. That is not just tidiness: it is what makes the boxes cheap. The Intel chip earns its place by transcoding; the Ryzen earns its place by running everything else.</p>
<h1 id="secrets-it-took-three-tries">Secrets: it took three tries</h1>
<p>I went through three secrets setups before landing on one I am happy with.</p>
<ol>
<li><strong>SOPS with age</strong> for host secrets. Clean, keyed off each machine's SSH host key, no extra service.</li>
<li><strong>Sealed Secrets</strong> for cluster secrets. Encrypted in git, decrypted in-cluster. Fine, until you have two clusters: each has its own sealing key, so every secret has to be sealed per cluster, and losing a cluster loses its key.</li>
<li><strong>Bitwarden Secrets Manager with External Secrets Operator</strong>. The store is <strong>outside</strong> the lab, so a cluster wipe does not lose it. An ExternalSecret in git pulls the value at sync time, so rotating a credential is editing it in Bitwarden, not re-sealing a blob and committing it.</li>
</ol>
<p>The last one is the reason the rebuild story works at all, and I would start here if I did it again.</p>
<h1 id="being-able-to-rebuild-it">Being able to rebuild it</h1>
<p>The test I kept asking myself was blunt: if both boxes die tomorrow, what do I actually need to have kept?</p>
<p>The answer turned out to be surprisingly small. Everything declarative (the OS flake, the cluster state, the app manifests) is in git. Everything secret is in Bitwarden. The only things that are neither are the <strong>keys to those two things</strong>, so I back those up: the host SSH keys (which are the SOPS identity), the Kubernetes kubeconfig, the Bitwarden machine token, encrypted and pushed to object storage on a timer. A rebuild is then a small, written-down sequence rather than an archaeology project.</p>
<h1 id="identity-and-the-front-door">Identity and the front door</h1>
<p>The tunnel is the only way in, and Cloudflare Access sits in front of it, so a request has to be <strong>someone</strong> before it reaches an app. Where an app can consume the identity Access passes along, I let it, so each person gets their own account. Where it cannot (the download clients, mostly), it is one shared login. That hybrid is honest about the tools: you get single sign-on where it exists, and a shared door where it does not.</p>
<h1 id="what-i-would-still-change">What I would still change</h1>
<p>The current front door trusts the identity header Access injects. That is only safe if the origin is reachable <strong>only</strong> through the tunnel, and right now the services are also reachable on the LAN, so the header can be forged there. The fix is to make the tunnel the only path in (or validate the signed token at the edge). I have not done it yet, and I would not call the lab fully hardened until I do.</p>
<p>The whole thing is a work in progress, which is the point. Each layer is replaceable: the OS, the cluster, the secrets, the edge. Swapping any one of them does not require touching the rest, and that is the property I was actually after.</p>
]]></content:encoded></item></channel></rss>