<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <docs>https://blogs.law.harvard.edu/tech/rss</docs>
    <title>GitOps on Fuchsbau</title>
    <link>https://this-is-fine.io/tags/gitops/</link>
    <description>Recent content in GitOps on Fuchsbau</description>
    <image>
      <title>GitOps on Fuchsbau</title>
      <link>https://this-is-fine.io/tags/gitops/</link>
      <url>https://source.unsplash.com/2000x1322/?fox</url>
    </image>
    <ttl>1440</ttl>
    <generator>Hugo 0.125.4</generator>
    <language>de-DE</language>
    <lastBuildDate>Wed, 20 May 2026 22:26:11 UT</lastBuildDate>
    <atom:link href="https://this-is-fine.io/tags/gitops/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>From Mastodon to snac: A Lighter Fediverse Stack</title>
      <link>https://this-is-fine.io/posts/20260520-mastodon-to-snac/</link>
      <pubDate>Wed, 20 May 2026 10:00:00 UT</pubDate>
      <dc:creator>ff0x</dc:creator>
      <guid>https://this-is-fine.io/posts/20260520-mastodon-to-snac/</guid>
      <description>The lab used to run Mastodon on Kubernetes: PostgreSQL, a Redis-compatible cache, and S3-shaped media storage on top of the usual edge stack. That worked, but it was heavy for a small personal instance — more moving parts than the workload justified.
The fediverse home at this-is-fine.social now runs snac instead: a minimal ActivityPub server in portable C, no database, all state on a single filesystem tree, backed up off-cluster with the same VolSync &#43; restic pattern as other stateful apps.
</description>
      <category domain="https://this-is-fine.io/categories/infrastructure">Infrastructure</category>
      <content:encoded><![CDATA[The lab used to run Mastodon on Kubernetes: PostgreSQL, a Redis-compatible cache, and S3-shaped media storage on top of the usual edge stack. That worked, but it was heavy for a small personal instance — more moving parts than the workload justified.
The fediverse home at this-is-fine.social now runs snac instead: a minimal ActivityPub server in portable C, no database, all state on a single filesystem tree, backed up off-cluster with the same VolSync &#43; restic pattern as other stateful apps.
Why leave Mastodon? The earlier write-up on running Mastodon in the lab listed the real cost:
Layer Mastodon stack Operational weight App Mastodon (web &#43; streaming &#43; sidekiq) Large images, many processes SQL CloudNative-PG Cluster, backups, upgrades Cache Dragonfly (Redis API) Extra Deployment, TLS to cache Media Rook RGW / object storage Buckets, credentials, S3 quirks Edge Gateway API, cert-manager, DNS Still required for federation For a handful of local users and light federation, that is a lot of infrastructure to babysit. Mastodon is excellent software; it is simply bloated relative to “I want a fediverse account on my own domain without running a small SaaS platform.”
What snac changes snac (“Social Networks Are Crap”) is deliberately small:
No PostgreSQL, no Redis, no object store — posts, follows, and media live under one data directory. ActivityPub federation to Mastodon, Pleroma, and friends; optional Mastodon API so many client apps still work. Hard-link–friendly on-disk layout (documented in snac(8)); backups must preserve that (tar/rsync -H). The lab builds a container from images/snac/ (Alpine, snac httpd via a small entrypoint). A PVC mounts at /snac/data; Gateway API and TLS stay the same story as in the Envoy Gateway post.
Internet -&amp;gt; this-is-fine.social (HTTPRoute) -&amp;gt; snac (Deployment, port 8001) PVC: server.json, users/, media/ (one tree) -&amp;gt; VolSync restic -&amp;gt; remote repository Backups without a database dump Stateful Mastodon meant CNPG backups plus optional PVC copies. With snac, the backup surface collapses to the PVC:
Include k8s/templates/volsync/ in the snac app Kustomization (same as VolSync article). Point the Deployment volume at the template’s claimName. Let the restic mover snapshot and push encrypted data to the shared remote repo. Restic sees a consistent filesystem snapshot; there is no separate “media bucket” or SQL dump to coordinate. Restore is “recover the PVC tree, start snac httpd,” respecting hard links on restore.
Account migration (Mastodon -&amp;gt; snac) snac documents a Mastodon-to-snac move in snac(8) (since 2.61). In short:
On the old Mastodon account, export follows, lists, blocks, and bookmarks (CSV). Copy the CSV files into the snac user’s import/ directory and run snac import_csv. Run snac alias to tie the new snac user to the old @user@old.instance handle. On Mastodon, use Move to a different account and point followers at the snac handle. The upstream manual and fedi.tips migration guide are worth reading before you cut traffic — federation moves depend on remote servers cooperating.
Tradeoffs snac is not a drop-in replacement for every Mastodon feature. Lists, moderation tooling, and admin UX are thinner; the web UI is simple by design. For this lab that is acceptable: less software, fewer night pages, same domain on the fediverse.
GitOps layout (Flux overview) is unchanged — only the application bundle under k8s/applications/ swapped from the Mastodon Helm release to a slim snac Deployment. The heavy chart stack can stay retired unless something else needs it.
Further reading: snac README, ActivityPub, prior Mastodon lab stack.
]]></content:encoded>
    </item>
    <item>
      <title>Pocket ID as the Lab OIDC Provider (Zot Example)</title>
      <link>https://this-is-fine.io/posts/20251122-pocket-id-oidc-zot/</link>
      <pubDate>Sat, 22 Nov 2025 11:00:00 UT</pubDate>
      <dc:creator>ff0x</dc:creator>
      <guid>https://this-is-fine.io/posts/20251122-pocket-id-oidc-zot/</guid>
      <description>Pocket ID is a small OpenID Connect provider — enough for a homelab without Keycloak. The lab runs it at https://auth.this-is-fine.io with Flux and an HTTPRoute on Envoy Gateway. Below is how Zot uses native OpenID against that issuer.
If you already run a homelab IdP, the interesting part is how little application code must change when the app speaks OIDC natively. Zot is a clear example: configure issuer URL, client ID, and redirect URIs, mount a secret, and the registry UI handles login without an OAuth sidecar in front of it.
</description>
      <category domain="https://this-is-fine.io/categories/infrastructure">Infrastructure</category>
      <content:encoded><![CDATA[Pocket ID is a small OpenID Connect provider — enough for a homelab without Keycloak. The lab runs it at https://auth.this-is-fine.io with Flux and an HTTPRoute on Envoy Gateway. Below is how Zot uses native OpenID against that issuer.
If you already run a homelab IdP, the interesting part is how little application code must change when the app speaks OIDC natively. Zot is a clear example: configure issuer URL, client ID, and redirect URIs, mount a secret, and the registry UI handles login without an OAuth sidecar in front of it.
Software map Piece Link Role Pocket ID pocket-id.org Users, OAuth clients, consent UI OIDC primer OpenID Connect Issuer, client_id, redirect URIs Zot OpenID Zot auth Registry login Pocket ID on the cluster browser -&amp;gt; auth.this-is-fine.io -&amp;gt; Pocket ID (PVC-backed) -&amp;gt; /.well-known/openid-configuration Public route on shared-gateway-external; TRUST_PROXY=true behind Cloudflare. Optional VolSync backup for the PVC. Encryption key and client credentials via SOPS, same as other apps. Register each OAuth client in Pocket ID (redirect URL and scopes) before switching the app on. A mismatch on redirect URI is the most common first-login failure; fix it in Pocket ID, not in Zot’s logs alone.
Zot: native OpenID Zot at oci.this-is-fine.io reads OpenID from zot.json — no Envoy OIDC shim for the registry UI.
&amp;#34;openid&amp;#34;: { &amp;#34;providers&amp;#34;: { &amp;#34;oidc&amp;#34;: { &amp;#34;issuer&amp;#34;: &amp;#34;https://auth.this-is-fine.io&amp;#34;, &amp;#34;scopes&amp;#34;: [&amp;#34;openid&amp;#34;, &amp;#34;profile&amp;#34;, &amp;#34;email&amp;#34;] } } } A Kubernetes Secret mounts the client credentials file. htpasswd stays for robots (buildbot, CI); people use SSO. RBAC maps OIDC groups (admins, users) to registry roles.
human -&amp;gt; oci.this-is-fine.io -&amp;gt; redirect -&amp;gt; auth.this-is-fine.io &amp;lt;- authorization code / token automation -&amp;gt; htpasswd or robot account (unchanged) People through Pocket ID; automation through scoped local accounts — a useful split elsewhere too.
Headscale OIDC is on the backlog (Envoy post notes the TODO): the chart already has placeholder environment variables; enabling them means registering another client in Pocket ID and trusting the same issuer URL.
Related Zot vs Harbor Cosign enforcement Lab GitOps Pocket ID: installation, OIDC clients.
]]></content:encoded>
    </item>
    <item>
      <title>Envoy Gateway and the Move from Traefik to Gateway API</title>
      <link>https://this-is-fine.io/posts/20251122-envoy-gateway-headscale-routes/</link>
      <pubDate>Sat, 22 Nov 2025 08:00:00 UT</pubDate>
      <dc:creator>ff0x</dc:creator>
      <guid>https://this-is-fine.io/posts/20251122-envoy-gateway-headscale-routes/</guid>
      <description>The lab used to run Traefik with its own CRDs (IngressRoute, Middleware, and friends). Gateway API standardises routes; Envoy Gateway is the controller here — one Helm install, three shared Gateways, and per-app HTTPRoute resources instead of Traefik-only objects.
Traefik is excellent at the edge, but its CRDs are controller-specific. Moving to Gateway API was less about feature envy and more about portability: the same HTTPRoute can be read by another implementation if you ever switch controllers. Envoy Gateway is the implementation here; the routes stay standard Kubernetes objects.
</description>
      <category domain="https://this-is-fine.io/categories/infrastructure">Infrastructure</category>
      <content:encoded><![CDATA[The lab used to run Traefik with its own CRDs (IngressRoute, Middleware, and friends). Gateway API standardises routes; Envoy Gateway is the controller here — one Helm install, three shared Gateways, and per-app HTTPRoute resources instead of Traefik-only objects.
Traefik is excellent at the edge, but its CRDs are controller-specific. Moving to Gateway API was less about feature envy and more about portability: the same HTTPRoute can be read by another implementation if you ever switch controllers. Envoy Gateway is the implementation here; the routes stay standard Kubernetes objects.
Traefik habits vs Gateway API Traefik Gateway API IngressRoute &#43; entrypoints HTTPRoute &#43; parentRefs on a Gateway listener Per-app TLS / middleware CRDs TLS on the Gateway; filters on HTTPRoute; Envoy policies when needed Traefik host rules hostnames and matches on gateway.networking.k8s.io/v1 Routes stay portable across controllers. Envoy-specific tuning uses gateway.envoyproxy.io (BackendTrafficPolicy, ClientTrafficPolicy, &amp;hellip;).
Three shared gateways Envoy Gateway (Helm) shared-gateway-external *.this-is-fine.io, *.this-is-fine.social Let&amp;#39;s Encrypt, LB .0.1 shared-gateway-internal *.this-is-fine.internal Vault PKI, LB .0.2 shared-gateway-tailnet *.tif.internal Vault PKI, LB .0.3 Pick the gateway by who connects: the internet, lab browsers on the internal CA, or tailnet clients. Templates under k8s/templates/gateway-api/ repeat the same HTTPRoute shape for each class.
Static LoadBalancer IPs (.0.1, .0.2, .0.3) come from a small pool advertised with Cilium BGP. DNS and certificates differ per zone, but the dataplane pattern is the same: one Envoy deployment, several logical gateways, many HTTPRoutes attached to listeners.
Headscale example (public API, private UI) Headscale disables Helm ingress; exposure is all Gateway API.
Surface Hostname Gateway Backend Tailscale clients / API ts.this-is-fine.io shared-gateway-external :8080 Web UI (operators) ts.this-is-fine.internal shared-gateway-internal :8081 (UI sidecar) The external route allows CORS from https://ts.this-is-fine.internal. Control traffic needs WebSocket upgrades; a BackendTrafficPolicy on that HTTPRoute enables tailscale-control-protocol and derp.
Splitting API and UI across external and internal DNS is deliberate. Tailscale clients and the public internet need ts.this-is-fine.io with a certificate browsers and devices trust. Operators can open the same logical service on ts.this-is-fine.internal with the lab’s internal CA, without publishing the admin UI to the open internet.
Public API (lab pattern):
apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: headscale-https-external spec: parentRefs: - name: shared-gateway-external namespace: envoy-gateway-system sectionName: this-is-fine-io-https hostnames: - ts.this-is-fine.io rules: - backendRefs: - name: headscale port: 8080 Internal UI — same host label on the internal zone, different listener and port:
apiVersion: gateway.networking.k8s.io/v1 kind: HTTPRoute metadata: name: headscale-ui-https-internal spec: parentRefs: - name: shared-gateway-internal namespace: envoy-gateway-system sectionName: this-is-fine-internal-https hostnames: - ts.this-is-fine.internal rules: - backendRefs: - name: headscale port: 8081 Helm sets HEADSCALE_SERVER_URL=https://ts.this-is-fine.io so clients and the UI agree on the public URL while operators open the UI on the internal name.
TODO: connect Pocket ID for Headscale OIDC (HEADSCALE_OIDC_* is stubbed in chart values; not enabled yet). Same backlog as Zeroclaw UI and Vault.
Adding another app Expose the workload with a Service. Attach an HTTPRoute (or TCPRoute) to the right shared-gateway-* and hostname. Let cert-manager issue TLS via the gateway certificateRefs / ClusterIssuers. IRC on port 6697 uses TCPRoute on the public gateway — IRC post. VIPs .0.1–.0.3 are advertised with Cilium BGP. GitOps context: lab overview.
]]></content:encoded>
    </item>
    <item>
      <title>Cosign and Kyverno for ZeroClaw Container Images</title>
      <link>https://this-is-fine.io/posts/20251121-cosign-kyverno-zeroclaw/</link>
      <pubDate>Fri, 21 Nov 2025 12:00:00 UT</pubDate>
      <dc:creator>ff0x</dc:creator>
      <guid>https://this-is-fine.io/posts/20251121-cosign-kyverno-zeroclaw/</guid>
      <description>For images you build yourself, a practical supply-chain loop is: build in CI, sign the digest, verify at admission. The lab uses Cosign (Sigstore), a private Zot registry at oci.this-is-fine.io, and Kyverno verifyImages so unsigned ZeroClaw pods do not start.
CI holds the private signing key; the cluster policy carries the matching public key.
Signing answers a simple question: did this image come from your build pipeline? Scanning for CVEs is still worth doing, but signature verification stops casual image substitution even when a tag name looks familiar.
</description>
      <category domain="https://this-is-fine.io/categories/infrastructure">Infrastructure</category>
      <category domain="https://this-is-fine.io/categories/security">Security</category>
      <content:encoded><![CDATA[For images you build yourself, a practical supply-chain loop is: build in CI, sign the digest, verify at admission. The lab uses Cosign (Sigstore), a private Zot registry at oci.this-is-fine.io, and Kyverno verifyImages so unsigned ZeroClaw pods do not start.
CI holds the private signing key; the cluster policy carries the matching public key.
Signing answers a simple question: did this image come from your build pipeline? Scanning for CVEs is still worth doing, but signature verification stops casual image substitution even when a tag name looks familiar.
Concepts Tool What you learn Cosign Sign OCI digests; signatures stay with the image Zot OCI registry that stores signature artifacts Kyverno Admission policy; verifyImages runs Cosign verify Keyless (Flux) Sigstore OIDC — separate rule for Flux controller images CI pipeline git push -&amp;gt; build image -&amp;gt; push to oci.this-is-fine.io/zeroclaw/... -&amp;gt; cosign sign digest (sha256:...) Sign the digest, not only a moving tag. Tags like :latest can be repointed; a signature on sha256:… stays tied to the bits you tested in CI.
Forge CI builds multi-arch images when needed, pushes with skopeo, then signs. Zot stores the signature artifact next to the image so cosign verify works from a laptop or from Kyverno inside the cluster.
Admission Pod CREATE -&amp;gt; Kyverno checks oci.this-is-fine.io/* -&amp;gt; cosign verify (public key in ClusterPolicy) -&amp;gt; reject OR pull and start Two policies in practice: a static key for images you build (ZeroClaw, workspace, and anything else on Zot); keyless verification for upstream Flux controllers. Do not mix the rules.
When verification fails, the Pod never starts. kubectl describe on the ReplicaSet usually points at Kyverno; PolicyReport resources summarize which rule blocked the image. Fixing it means either signing the image you meant to run or adjusting the policy — not disabling admission quietly.
Rotation means a new key pair, updated CI secret, updated policy PEM, and re-signed digests you still deploy. References: Cosign keys, Kyverno verifyImages.
]]></content:encoded>
    </item>
    <item>
      <title>IRC in the Lab: InspIRCd and a ZNC Bouncer</title>
      <link>https://this-is-fine.io/posts/20251121-irc-inspircd-znc/</link>
      <pubDate>Fri, 21 Nov 2025 10:00:00 UT</pubDate>
      <dc:creator>ff0x</dc:creator>
      <guid>https://this-is-fine.io/posts/20251121-irc-inspircd-znc/</guid>
      <description>IRC is still a simple model: one server, many channels, text over a long-lived TCP session. InspIRCd runs the network; ZNC is a bouncer that stays logged in when your client disconnects and replays backlog when you return.
In the lab both are plain Deployments under Flux, with TLS from cert-manager and a Gateway API TCPRoute for TLS on port 6697.
IRC is a good contrast to HTTP-only homelab apps: clients expect a long-lived TCP session and often TLS on a non-443 port. Gateway API TCPRoute covers that without bolting IRC into an HTTP ingress controller as a special case.
</description>
      <category domain="https://this-is-fine.io/categories/infrastructure">Infrastructure</category>
      <content:encoded><![CDATA[IRC is still a simple model: one server, many channels, text over a long-lived TCP session. InspIRCd runs the network; ZNC is a bouncer that stays logged in when your client disconnects and replays backlog when you return.
In the lab both are plain Deployments under Flux, with TLS from cert-manager and a Gateway API TCPRoute for TLS on port 6697.
IRC is a good contrast to HTTP-only homelab apps: clients expect a long-lived TCP session and often TLS on a non-443 port. Gateway API TCPRoute covers that without bolting IRC into an HTTP ingress controller as a special case.
Traffic path IRC client (TLS, port 6697) -&amp;gt; znc.this-is-fine.io (Envoy TCPRoute) -&amp;gt; ZNC (state on PVC) -&amp;gt; InspIRCd (same namespace) Daemon Docs Role InspIRCd docs.inspircd.org Channels, users, server links ZNC wiki.znc.bz Persistent session and backlog InspIRCd owns the network name and channels. ZNC sits in front for humans: you connect to ZNC with your IRC client, ZNC stays connected to InspIRCd, and you can disconnect overnight without losing channel context. Only the bouncer needs a PVC; the IRC daemon can stay ephemeral apart from config secrets.
TLS and gateways Port 6697 is the usual TLS IRC port (IRCS). The public bouncer uses an Envoy TCP listener and a cert-manager certificate for znc.this-is-fine.io. An optional admin UI, if enabled, sits on *.this-is-fine.internal like other lab apps — see the DNS overview.
Operator and bridge passwords are SOPS-encrypted in Git.
Extras ZNC data can use the VolSync &#43; restic template like any PVC-backed app. An optional Tor sidecar exists in Git for alternate exposure. Deeper setup: InspIRCd configuration wiki, ZNC setup guide.
]]></content:encoded>
    </item>
    <item>
      <title>Cluster-Wide Tailscale: Headscale, Tailnet DNS, and Cross-Cluster Routes</title>
      <link>https://this-is-fine.io/posts/20251121-tailscale-headscale-mesh/</link>
      <pubDate>Fri, 21 Nov 2025 09:00:00 UT</pubDate>
      <dc:creator>ff0x</dc:creator>
      <guid>https://this-is-fine.io/posts/20251121-tailscale-headscale-mesh/</guid>
      <description>Tailscale builds a WireGuard mesh with little configuration. Headscale is an open control server for the same clients — you run policy and issue keys yourself. The lab does not use the Tailscale Kubernetes operator; a handful of Deployments and DaemonSets do the job instead.
The goal is one tailnet for laptops and nodes, with Kubernetes APIs and internal HTTP on *.tif.internal without putting those names on the public internet.
Self-hosting the control plane means you own ACL files, preauth keys, and MagicDNS base domains. The trade-off is operational work: upgrades, backups, and policy edits are yours. For a multi-cluster lab that already runs GitOps everywhere else, that trade-off is acceptable.
</description>
      <category domain="https://this-is-fine.io/categories/infrastructure">Infrastructure</category>
      <content:encoded><![CDATA[Tailscale builds a WireGuard mesh with little configuration. Headscale is an open control server for the same clients — you run policy and issue keys yourself. The lab does not use the Tailscale Kubernetes operator; a handful of Deployments and DaemonSets do the job instead.
The goal is one tailnet for laptops and nodes, with Kubernetes APIs and internal HTTP on *.tif.internal without putting those names on the public internet.
Self-hosting the control plane means you own ACL files, preauth keys, and MagicDNS base domains. The trade-off is operational work: upgrades, backups, and policy edits are yours. For a multi-cluster lab that already runs GitOps everywhere else, that trade-off is acceptable.
Read first Piece Docs Headscale headscale.net Talos Tailscale extension Talos network guide Gateway API gateway-api.sigs.k8s.io Envoy Gateway gateway.envoyproxy.io Public control plane URL: ts.this-is-fine.io. MagicDNS suffix: tif.internal.
What joins the mesh Headscale (ts.this-is-fine.io) Talos nodes (Tailscale extension) kube-apiserver-proxy per cluster (TCP 6443 on tailnet) tailnet-dns (pods resolve *.tif.internal) address space: 100.64.0.0/10 kube-apiserver-proxy runs tailscaled and socat. Tailnet clients use hydra-k8s.tif.internal:6443 (pattern: &amp;lt;cluster&amp;gt;-k8s.tif.internal) to reach the cluster API VIP. tailnet-dns forwards *.tif.internal to Headscale MagicDNS from inside the cluster.
Pods do not run a Tailscale sidecar in this design. Workloads that need the Kubernetes API or an internal service use cluster DNS first. The tailnet-dns DaemonSet patches the chain so *.tif.internal resolves the same way your laptop would on the mesh. Egress from the pod still leaves via the node’s tailnet identity, so ACLs must allow that path.
HTTP on the tailnet Attach an HTTPRoute to shared-gateway-tailnet. Example hostname: vault.tif.internal -&amp;gt; Envoy -&amp;gt; Service. TLS comes from in-cluster PKI via cert-manager.
tailnet client (HTTPS) -&amp;gt; vault.tif.internal -&amp;gt; shared-gateway-tailnet -&amp;gt; Service -&amp;gt; Pods Public sites use a different gateway (this-is-fine.io, Let&amp;rsquo;s Encrypt). Keep listeners separate so you do not accidentally expose an internal-only app.
Policy (sketch) Headscale ACLs use tags (k8s-api, fabric, per-cluster node tags) and auto-approved routes for the LoadBalancer slice that fronts the tailnet gateway. Join keys are SOPS-encrypted bootstrap secrets.
Operator UI: ts.this-is-fine.internal on port 8081 through the internal gateway.
Part of the wider GitOps lab. IRC TLS on the public gateway is covered in the IRC post.
]]></content:encoded>
    </item>
    <item>
      <title>ZeroClaw in the Lab: a GitOps Agent on Matrix</title>
      <link>https://this-is-fine.io/posts/20251120-zeroclaw-in-the-lab/</link>
      <pubDate>Thu, 20 Nov 2025 09:00:00 UT</pubDate>
      <dc:creator>ff0x</dc:creator>
      <guid>https://this-is-fine.io/posts/20251120-zeroclaw-in-the-lab/</guid>
      <description>ZeroClaw (nickname Claw) is a small AI agent for cluster work: check Flux status, explain failing pods, dry-run Renovate, draft Git patches. It shares the same GitOps monorepo as the cluster — it is not a second control plane.
You talk to it on Matrix at matrix.this-is-fine.social. Web fetches go through MCP to a Scrapling sidecar instead of built-in browser tools, so there is one audited path for HTTP.
The agent is deliberately ops-focused. It is not a general chatbot for the public internet; it reads cluster state, follows skills checked into Git, and proposes changes that still pass human review. That keeps expectations aligned with what automation can safely do inside a production-shaped lab.
</description>
      <category domain="https://this-is-fine.io/categories/infrastructure">Infrastructure</category>
      <content:encoded><![CDATA[ZeroClaw (nickname Claw) is a small AI agent for cluster work: check Flux status, explain failing pods, dry-run Renovate, draft Git patches. It shares the same GitOps monorepo as the cluster — it is not a second control plane.
You talk to it on Matrix at matrix.this-is-fine.social. Web fetches go through MCP to a Scrapling sidecar instead of built-in browser tools, so there is one audited path for HTTP.
The agent is deliberately ops-focused. It is not a general chatbot for the public internet; it reads cluster state, follows skills checked into Git, and proposes changes that still pass human review. That keeps expectations aligned with what automation can safely do inside a production-shaped lab.
Concepts worth knowing Topic Pointer GitOps agent Policy and skills in Git; credentials mounted read-only at runtime Agent Skills Short SKILL.md playbooks instead of huge prompts MCP Sidecar on loopback; the agent calls tools over HTTP Matrix E2EE Crypto state on a PVC — not inside the workspace seed image Skills are the main teaching device. Each skill is a short markdown playbook (flux debugging, storage checks, Renovate dry-runs) so the model reaches for a documented procedure instead of improvising shell one-liners.
Flow operator -&amp;gt; Matrix chat -&amp;gt; ZeroClaw pod (kubectl, flux, skills) -&amp;gt; MCP -&amp;gt; Scrapling (web) -&amp;gt; git patch on forge -&amp;gt; merge -&amp;gt; Flux reconciles Pod layout init: copy OCI workspace seed onto PVC containers: zeroclaw (daemon) scrapling :8000 (MCP) mounts: kubeconfig, registry auth, matrix, sops-age HTTPRoute: claw.this-is-fine.internal An ImageVolume refreshes workspace files from oci.this-is-fine.io/zeroclaw/workspace on each start. Conversation memory and Matrix state/ stay on the PVC. API access to other clusters uses normal pod DNS and *.tif.internal (see the tailnet post).
Forge CI builds and signs images — Cosign and Kyverno. The workspace image can refresh on every pod start while chat history and Matrix crypto keys persist on disk, which is the usual split between replaceable config and state you must not wipe casually.
Deliberate limits Changes go through a cloned repo and review, not edits on the live PVC tree. Built-in web_fetch and browser tools are disabled; Scrapling handles the web. Effective permissions follow kubeconfig RBAC; AGENTS.md gates destructive actions. Claw is a readable layer on top of Flux: ask in chat, confirm with flux get, land the fix in Git. If Flux says the cluster is healthy but an app misbehaves, the agent still has to read events and logs like anyone else — it just lowers the friction for doing that often.
]]></content:encoded>
    </item>
    <item>
      <title>Lab GitOps: Talos, Flux, and a Monorepo</title>
      <link>https://this-is-fine.io/posts/20251120-lab-gitops-flux-talos/</link>
      <pubDate>Thu, 20 Nov 2025 08:00:00 UT</pubDate>
      <dc:creator>ff0x</dc:creator>
      <guid>https://this-is-fine.io/posts/20251120-lab-gitops-flux-talos/</guid>
      <description>The lab is a Kubernetes testbed: try cluster layouts here before they go to production. After a short manual bootstrap, Flux keeps the cluster in sync with Git — the usual GitOps loop of declared state in version control and controllers that apply diffs.
Nodes run Talos Linux on Turing Pi RK1 boards. Images come from the Talos Image Factory (metal-arm64, sbc-rockchip / turingrk1). Talos keeps the node OS small and API-driven: you do not SSH in to patch packages. That pushes complexity into Kubernetes manifests, which fits a GitOps workflow.
Everything after the first bootstrap lives in one monorepo: cluster overlays, shared operators, Helm charts, and application kustomizations. Talos machine config (talconfig) is versioned there too, but applied with talosctl, not Flux. Drawing that line clearly avoids pretending the entire world is reconciled from Git when the hypervisor and disk layout are still operator steps.
</description>
      <category domain="https://this-is-fine.io/categories/infrastructure">Infrastructure</category>
      <content:encoded><![CDATA[The lab is a Kubernetes testbed: try cluster layouts here before they go to production. After a short manual bootstrap, Flux keeps the cluster in sync with Git — the usual GitOps loop of declared state in version control and controllers that apply diffs.
Nodes run Talos Linux on Turing Pi RK1 boards. Images come from the Talos Image Factory (metal-arm64, sbc-rockchip / turingrk1). Talos keeps the node OS small and API-driven: you do not SSH in to patch packages. That pushes complexity into Kubernetes manifests, which fits a GitOps workflow.
Everything after the first bootstrap lives in one monorepo: cluster overlays, shared operators, Helm charts, and application kustomizations. Talos machine config (talconfig) is versioned there too, but applied with talosctl, not Flux. Drawing that line clearly avoids pretending the entire world is reconciled from Git when the hypervisor and disk layout are still operator steps.
Stack (what each piece teaches) Piece Learn more Talos API-driven, immutable nodes — no SSH shell on hosts Flux GitRepository, Kustomization, HelmRelease Kustomize &#43; Helm Overlays vs charts — choose per app SOPS &#43; AGE Encrypted secrets in Git; decrypted at reconcile Vault &#43; External Secrets Long-lived credentials outside plain Git Cilium CNI; BGP can advertise service LoadBalancer ranges Gateway API &#43; Envoy Gateway HTTP and TCP ingress cert-manager ACME and in-cluster CAs Rook-Ceph Block, filesystem, and S3-style storage Renovate Automated bumps for image and chart pins Bootstrap, then GitOps flash Talos (RK1) -&amp;gt; talosctl bootstrap -&amp;gt; flux bootstrap Git becomes the source of truth One monorepo holds shared common/ operators and per-cluster overlays. The GitRepository uses an ignore block so Flux does not clone agent code, docs, or unrelated trees on every reconcile. That keeps sync intervals reasonable on modest hardware.
How a change rolls out Git branch (main) -&amp;gt; GitRepository (poll) -&amp;gt; common layer (Flux, Cilium, cert-manager, ...) -&amp;gt; repo definitions (Helm/OCI) -&amp;gt; app overlays (your workloads) -&amp;gt; HelmRelease, HTTPRoute, PVC, ExternalSecret, ... dependsOn orders that graph (storage before apps, for example). postBuild substitution reads common-config and fills hostnames — this-is-fine.io, this-is-fine.social, this-is-fine.internal, tif.internal — so the same manifests work on every cluster without per-site forks.
Bootstrap secrets use SOPS; runtime passwords and tokens live in Vault and sync through External Secrets. The split mirrors how often values change: bootstrap keys rotate rarely; database passwords and API tokens churn more often and are easier to audit in Vault.
Renovate watches the same repo and opens bump PRs for container images, Helm charts, and Talos or Flux pins. That closes the loop so the lab does not silently fall behind upstream.
DNS zones (quick map) this-is-fine.io public internet (ACME, external gateway) this-is-fine.social fediverse this-is-fine.internal lab UIs (internal gateway, Vault PKI) tif.internal Headscale tailnet (MagicDNS) Takeaway Talos limits the OS problem; Flux limits the “what is actually deployed?” problem. Together they give a repeatable lab: flash boards, bootstrap once per cluster, then iterate by merge request like any other software project.
More on this setup: ZeroClaw, Envoy Gateway, Cilium BGP, Zot, Pocket ID, backups, tailnet, image signing.
]]></content:encoded>
    </item>
  </channel>
</rss>
