<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Cloud Rumble Blog</title>
        <link>https://frosty-babbage-3125a3.netlify.app/blog</link>
        <description>Cloud Rumble Blog</description>
        <lastBuildDate>Tue, 24 Feb 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[Your AI Agent Should Read Your Notes Before Answering]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes</guid>
            <pubDate>Tue, 24 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[R2R on Kubernetes, pgvector for storage, Ollama for embeddings, and a Python hook that searches your knowledge on every prompt in under 100ms.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="RAG Architecture" src="https://frosty-babbage-3125a3.netlify.app/assets/images/01-architecture-1ba1662b18b37a74e9180038571a2312.png" width="2892" height="1935" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>I have 395 notes in Obsidian and ~2,800 memories from past AI sessions. My AI agent knows none of it unless I paste it in manually. And I can only paste what I remember to paste — which defeats the point of having a knowledge store.</p>
<p>So I built a hook that fires before every prompt, searches a unified vector store, and injects the relevant context before the agent thinks. R2R as the RAG backend, pgvector for storage, Ollama for embeddings, all on a homelab Kubernetes cluster. Total latency: under 100ms. Zero tokens consumed in the retrieval path.</p>
<p>The value isn't the search itself. It's what surfaces when you stop choosing what's relevant. Ask about a Kubernetes pattern and a six-month-old Obsidian note appears. Debug a tmux issue and a memory from a previous session shows up with the exact fix. The agent answers from your accumulated knowledge, not just its training data.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>Three backend services and one client-side hook.</p>
<p><img decoding="async" loading="lazy" alt="Architecture" src="https://frosty-babbage-3125a3.netlify.app/assets/images/01-architecture-1ba1662b18b37a74e9180038571a2312.png" width="2892" height="1935" class="img_ev3q"></p>
<p>Each prompt follows this path:</p>
<ol>
<li class="">You type a prompt in Claude Code</li>
<li class="">The <code>UserPromptSubmit</code> hook fires before the agent sees it</li>
<li class="">The hook searches R2R's unified index (~90ms) containing both Obsidian notes and agent memories</li>
<li class="">Relevant chunks get injected as <code>system-reminder</code> context</li>
<li class="">The agent sees your prompt plus the matched knowledge</li>
<li class="">If a note title looks relevant, the agent drills deeper via the Obsidian MCP server</li>
</ol>
<p>Two data sources feed a single vector store. A local cron syncs the Obsidian vault into R2R every 30 minutes. A Kubernetes CronJob syncs agent-memory entries every hour. Both land in pgvector with the same embedding model, so a single search covers your curated notes and your session-derived insights.</p>
<table><thead><tr><th>Component</th><th>Purpose</th><th>Storage</th><th>Latency</th></tr></thead><tbody><tr><td>R2R (SciPhi)</td><td>Unified search (notes + memories)</td><td>pgvector on K8s, 10Gi PVC</td><td>~90ms</td></tr><tr><td>Obsidian sync</td><td>Vault ingestion into R2R</td><td>Local cron, every 30min</td><td>batch</td></tr><tr><td>Memory sync</td><td>Agent-memory ingestion into R2R</td><td>K8s CronJob, every hour</td><td>batch</td></tr><tr><td>Ollama</td><td>nomic-embed-text embeddings</td><td>System76 Serval WS (RTX 5070 Ti)</td><td>&lt;100ms</td></tr><tr><td>Anthropic Haiku</td><td>R2R completions via LiteLLM</td><td>Cloud API</td><td>on-demand</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-vector-search-works">How Vector Search Works<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#how-vector-search-works" class="hash-link" aria-label="Direct link to How Vector Search Works" title="Direct link to How Vector Search Works" translate="no">​</a></h2>
<p>The search pipeline runs without an LLM. The key component is an embedding model: nomic-embed-text (274MB), running on Ollama. It converts text into a 768-dimensional vector — a list of 768 floating-point numbers that represent the text's meaning as coordinates in high-dimensional space.</p>
<p>The model was trained on millions of text pairs so that texts with similar meaning land near each other. "Longhorn PVC backup" and "persistent volume restore" end up as nearby points, even though they share zero words.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ingestion">Ingestion<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#ingestion" class="hash-link" aria-label="Direct link to Ingestion" title="Direct link to Ingestion" translate="no">​</a></h3>
<p>When the 395 Obsidian notes were loaded into R2R, each note got chunked into segments. Each chunk went to Ollama, which ran it through nomic-embed-text and returned a 768-number vector. pgvector stored both the vector and the original text:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">pgvector row:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  id:        uuid</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  text:      "StatefulSet gets a 10Gi Longhorn PVC..."</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  embedding: [0.023, -0.187, 0.442, ..., 0.091]  (768 floats)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  metadata:  {title: "r2r-rag-pipeline", source: "obsidian"}</span><br></span></code></pre></div></div>
<p>pgvector builds an HNSW index over these vectors so it doesn't compare against every row at query time.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="search">Search<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#search" class="hash-link" aria-label="Direct link to Search" title="Direct link to Search" translate="no">​</a></h3>
<p>When you type "how do I backup Longhorn volumes?", the hook sends your prompt to Ollama, gets back 768 numbers, and sends those to pgvector. pgvector computes cosine similarity — the angle between your prompt vector and every stored vector — and returns the closest matches. Identical direction = 1.0, orthogonal = 0.0. The threshold is 0.45; anything below gets dropped as irrelevant.</p>
<p>This is pure linear algebra. Dot products and normalization. That's why it runs in ~100ms with zero token costs.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-store-two-sources">One store, two sources<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#one-store-two-sources" class="hash-link" aria-label="Direct link to One store, two sources" title="Direct link to One store, two sources" translate="no">​</a></h3>
<p>Both Obsidian notes and agent memories get embedded with nomic-embed-text and stored in pgvector. A single HNSW index covers everything — a prompt about "Longhorn backup" finds relevant hits regardless of whether the knowledge came from a note you wrote or a pattern the agent learned.</p>
<table><thead><tr><th></th><th>Obsidian notes</th><th>Agent memories</th></tr></thead><tbody><tr><td>Sync method</td><td>Local cron (every 30min)</td><td>K8s CronJob (every hour)</td></tr><tr><td>Content</td><td>Curated technical knowledge</td><td>Session-derived insights</td></tr><tr><td>Chunking</td><td>R2R recursive splitter</td><td>Whole entries as documents</td></tr><tr><td>Metadata tag</td><td><code>source: obsidian</code></td><td><code>source: agent-memory</code></td></tr></tbody></table>
<p>The metadata tags let you filter by source if needed, but the default search spans both.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-database-journey">The Database Journey<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#the-database-journey" class="hash-link" aria-label="Direct link to The Database Journey" title="Direct link to The Database Journey" translate="no">​</a></h2>
<p>I started with the <a href="https://cloudnative-pg.io/" target="_blank" rel="noopener noreferrer" class="">CloudNativePG operator</a>. It's the standard for running Postgres on Kubernetes — WAL archiving, automated failover, point-in-time recovery. Production-grade.</p>
<p>It didn't work. Two problems.</p>
<p>First, the pgvector image. CNPG validates container images through a webhook, and <code>pgvector/pgvector:pg17</code> didn't match the expected image patterns. The webhook rejected the pod.</p>
<p>Second, ImageVolume extensions. CNPG has a mechanism for loading Postgres extensions via ephemeral volumes. The pgvector extension needs to be loaded as a shared library, and the ImageVolume approach hit path resolution issues on my cluster's containerd version.</p>
<p>I spent a day debugging webhook configurations and extension loading. Then I stopped and asked: what am I actually building?</p>
<p>A homelab. Single node. No HA requirement. No point-in-time recovery needed. The data is my Obsidian vault — I have the source of truth on disk. If the database dies, I re-ingest.</p>
<p>A plain StatefulSet with the <code>pgvector/pgvector:pg17</code> image works:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> apps/v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> StatefulSet</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> r2r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">db</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">serviceName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> r2r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">db</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">replicas</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">template</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">containers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> postgres</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> pgvector/pgvector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">pg17</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> PGDATA</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> /var/lib/postgresql/data/pgdata</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">volumeMounts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">mountPath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> /var/lib/postgresql/data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> init</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">mountPath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> /docker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">entrypoint</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">initdb.d</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> init</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">configMap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> r2r</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">db</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">init</span><br></span></code></pre></div></div>
<p>An init ConfigMap creates the vector extension:</p>
<div class="language-sql codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-sql codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">CREATE</span><span class="token plain"> EXTENSION </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">IF</span><span class="token plain"> </span><span class="token operator">NOT</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">EXISTS</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"vector"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><br></span></code></pre></div></div>
<p>The StatefulSet gets a 10Gi Longhorn PVC, a readiness probe on <code>pg_isready</code>, and a Service. Postgres starts, loads pgvector, R2R connects.</p>
<p>Simplicity wins on a homelab. Save the operator for production.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ingesting-the-obsidian-vault">Ingesting the Obsidian Vault<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#ingesting-the-obsidian-vault" class="hash-link" aria-label="Direct link to Ingesting the Obsidian Vault" title="Direct link to Ingesting the Obsidian Vault" translate="no">​</a></h2>
<p>R2R accepts documents through a multipart API. The ingestion script walks the vault directory and uploads each markdown file as <code>raw_text</code>. 395 notes, about three minutes.</p>
<p>Two gotchas during ingestion.</p>
<p><strong>Filenames as DocumentType.</strong> R2R parses the uploaded filename to determine document type. Obsidian's zettelkasten IDs (<code>1711476153-YXRZ.md</code>) worked fine. But filenames with special characters got parsed as unknown types. The fix: sanitize filenames before upload and always set the content type to <code>text/plain</code>.</p>
<p><strong>Document summary generation.</strong> R2R's default pipeline generates a summary for each ingested document by calling the configured LLM. For 395 documents, that meant 395 Haiku calls during ingestion. Slow, expensive, and unnecessary since I only use vector search, not summaries.</p>
<p>One line in the R2R config fixes it:</p>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token section punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token section section-name selector" style="color:rgb(255, 121, 198)">ingestion</span><span class="token section punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">provider</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">"</span><span class="token value attr-value inner-value">r2r</span><span class="token value attr-value">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">skip_document_summary</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">true</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="r2r-configuration">R2R Configuration<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#r2r-configuration" class="hash-link" aria-label="Direct link to R2R Configuration" title="Direct link to R2R Configuration" translate="no">​</a></h2>
<p>R2R uses LiteLLM under the hood, so you can mix providers. Embeddings run through Ollama (free, local). Haiku is configured as the completion LLM, but the hook only calls R2R's <code>/v3/retrieval/search</code> endpoint — pure vector similarity, no LLM in the loop. Haiku would only fire if you used R2R's RAG endpoint for synthesized answers or re-enabled document summaries.</p>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token section punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token section section-name selector" style="color:rgb(255, 121, 198)">completion</span><span class="token section punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">provider</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">"</span><span class="token value attr-value inner-value">litellm</span><span class="token value attr-value">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">concurrent_request_limit</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">16</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token section punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token section section-name selector" style="color:rgb(255, 121, 198)">app</span><span class="token section punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">quality_llm</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">"</span><span class="token value attr-value inner-value">anthropic/claude-3-5-haiku-latest</span><span class="token value attr-value">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">fast_llm</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">"</span><span class="token value attr-value inner-value">anthropic/claude-3-5-haiku-latest</span><span class="token value attr-value">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token section punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token section section-name selector" style="color:rgb(255, 121, 198)">embedding</span><span class="token section punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">provider</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">"</span><span class="token value attr-value inner-value">ollama</span><span class="token value attr-value">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">base_model</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">"</span><span class="token value attr-value inner-value">nomic-embed-text</span><span class="token value attr-value">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">base_dimension</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">768</span><br></span></code></pre></div></div>
<p>The R2R deployment points <code>OLLAMA_API_BASE</code> at the in-cluster Ollama service. Ollama runs on a Pop-OS desktop at <code>192.168.178.125</code>. A headless Service with manual Endpoints bridges it into the cluster:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Service</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ollama</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">pc</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">annotations</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">description</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Pop-OS desktop - always on"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">clusterIP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> None</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">11434</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">targetPort</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">11434</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> http</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Endpoints</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ollama</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">pc</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">subsets</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">addresses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">ip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 192.168.178.125</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">11434</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> http</span><br></span></code></pre></div></div>
<p>Any pod in the cluster reaches Ollama at <code>ollama-pc.ai-tools.svc:11434</code>. No port-forwarding, no NodePorts.</p>
<p>One gotcha: Ollama evicts models from VRAM after five minutes idle. The embedding model kept getting cold-loaded on every RAG request after an idle gap. The fix: <code>keep_alive: -1</code> in the embed request pins the model permanently.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-rag-hook">The RAG Hook<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#the-rag-hook" class="hash-link" aria-label="Direct link to The RAG Hook" title="Direct link to The RAG Hook" translate="no">​</a></h2>
<p>The core of the system is a Python script that Claude Code runs as a <code>UserPromptSubmit</code> hook. It fires on every non-trivial prompt automatically, with an explicit <code>:rag</code> suffix for verbose output. The hook searches R2R's unified index and prints the results to stdout, which Claude Code injects as context.</p>
<p>Hook registration in <code>~/.claude/settings.json</code>:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"hooks"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"UserPromptSubmit"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"command"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token property">"command"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"python3 ~/.claude/scripts/__rag_context_hook.py"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>The script is 233 lines of stdlib Python. No dependencies beyond what ships with Python 3. Three design decisions matter.</p>
<p><strong>Automatic with escape hatches.</strong> The first version was manual-only — you had to type <code>:rag</code> to trigger a search. That meant you only got context when you remembered to ask for it, which defeats the purpose of having a knowledge store. The current version fires on every prompt longer than 20 characters that isn't trivial banter ("ok", "thanks", "ship it"). A regex filter catches these short responses and skips the search. Append <code>:norag</code> to suppress auto-search on a specific prompt. Append <code>:rag</code> for verbose output with scores and timing.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">how do I backup longhorn volumes    → auto search, compact output</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">metallb config issue:rag            → verbose search with scores</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">just do it:norag                    → no search</span><br></span></code></pre></div></div>
<p><strong>Single source, single request.</strong> Both Obsidian notes and agent memories live in the same pgvector index. One HTTP call to R2R's <code>/v3/retrieval/search</code> endpoint covers everything. No thread pools, no parallel coordination, no partial failure handling. The search takes ~90ms.</p>
<div class="language-python codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-python codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">payload </span><span class="token operator">=</span><span class="token plain"> json</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">dumps</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"query"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> query</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">"search_settings"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"limit"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> limit</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">encode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">req </span><span class="token operator">=</span><span class="token plain"> urllib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">Request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    R2R_URL</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> data</span><span class="token operator">=</span><span class="token plain">payload</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    headers</span><span class="token operator">=</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token string" style="color:rgb(255, 121, 198)">"Content-Type"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"application/json"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">with</span><span class="token plain"> urllib</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">request</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">urlopen</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">req</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> timeout</span><span class="token operator">=</span><span class="token plain">timeout</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> context</span><span class="token operator">=</span><span class="token plain">_ssl_ctx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">as</span><span class="token plain"> resp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    body </span><span class="token operator">=</span><span class="token plain"> resp</span><span class="token punctuation" style="color:rgb(248, 248, 242)">.</span><span class="token plain">read</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p><strong>Tuned relevance threshold.</strong> The score threshold sits at 0.45 for both auto and manual modes — high enough to filter noise, low enough to catch useful tangential hits. Auto mode returns 2 results with a 5-second timeout. Manual mode returns 4 results with a 12-second timeout and retries.</p>
<p>The hook prints results in a format Claude Code injects as a <code>system-reminder</code>:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">RAG context (Obsidian vault):</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  [kubernetes-networking] (score: 0.782)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    Service mesh configuration requires...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">RAG context (agent memory):</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  [kubernetes, networking, debugging]</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    When troubleshooting DNS in pods, check...</span><br></span></code></pre></div></div>
<p>The agent sees this alongside the prompt. If a note title looks relevant, it reads the full note via the Obsidian MCP server, follows wikilinks, cross-references. The hook provides the signal; the agent decides how deep to go.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="results">Results<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#results" class="hash-link" aria-label="Direct link to Results" title="Direct link to Results" translate="no">​</a></h2>
<p>The hook adds about 90ms to each prompt. Imperceptible — the agent's thinking time dwarfs it.</p>
<table><thead><tr><th>Metric</th><th>Value</th></tr></thead><tbody><tr><td>R2R search latency</td><td>~90ms</td></tr><tr><td>Total hook latency</td><td>~90ms</td></tr><tr><td>Obsidian notes indexed</td><td>395</td></tr><tr><td>Agent memories searchable</td><td>~2,800</td></tr><tr><td>Combined documents in pgvector</td><td>~3,200</td></tr></tbody></table>
<p>The original architecture searched R2R and agent-memory in parallel — two stores, two HTTP calls, a ThreadPoolExecutor to coordinate them. Agent-memory went through the MCP JSON-RPC layer, adding ~1.7s of overhead. Bypassing MCP and querying Redis FT.SEARCH directly helped, but maintaining two search backends meant two failure modes, two timeout configs, and two relevance thresholds to tune.</p>
<p>The current architecture is simpler. Both data sources sync into R2R on a schedule. One search call covers everything. The hook went from a parallel coordinator to a single HTTP request.</p>
<p>The value shows up in unexpected moments. Ask about a Kubernetes pattern and the hook surfaces an Obsidian note you wrote six months ago. Start debugging a tmux issue and it finds a memory from a previous session where you solved something similar. The agent doesn't just answer from its training data — it answers from your accumulated knowledge.</p>
<p>The entire search pipeline is open source and runs locally. Ollama generates embeddings with nomic-embed-text. pgvector stores and searches vectors. The hook is 233 lines of stdlib Python. Zero API calls, zero tokens consumed, zero billing in the retrieval path. The only cloud dependency is Claude itself interpreting the results.</p>
<p>The two sources complement each other even though they share an index. Obsidian holds curated, structured notes — architecture decisions, tool configurations, blog drafts. Agent-memory holds organic, session-derived insights — "this user prefers bun over npm", "the homelab uses Longhorn for storage", "tmux layouts need xdotool without --sync flags." Together they give the agent both your deliberate knowledge and your implicit patterns.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="links">Links<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/24/rag-homelab-kubernetes#links" class="hash-link" aria-label="Direct link to Links" title="Direct link to Links" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://r2r-docs.sciphi.ai/" target="_blank" rel="noopener noreferrer" class="">R2R</a> -- RAG framework by SciPhi</li>
<li class=""><a href="https://github.com/pgvector/pgvector" target="_blank" rel="noopener noreferrer" class="">pgvector</a> -- vector similarity search for Postgres</li>
<li class=""><a href="https://github.com/redis/agent-memory-server" target="_blank" rel="noopener noreferrer" class="">Redis Agent Memory Server</a> -- MCP-native memory service</li>
<li class=""><a href="https://ollama.com/" target="_blank" rel="noopener noreferrer" class="">Ollama</a> -- local LLM inference</li>
<li class=""><a href="https://docs.anthropic.com/en/docs/claude-code/hooks" target="_blank" rel="noopener noreferrer" class="">Claude Code hooks</a> -- pre/post tool execution hooks</li>
<li class=""><a href="https://github.com/Piotr1215/homelab" target="_blank" rel="noopener noreferrer" class="">Homelab repo</a> -- full working manifests in <code>gitops/apps/r2r/</code></li>
</ul>]]></content:encoded>
            <category>homelab</category>
            <category>kubernetes</category>
            <category>ai</category>
            <category>rag</category>
            <category>r2r</category>
            <category>ollama</category>
            <category>mcp</category>
            <category>gitops</category>
        </item>
        <item>
            <title><![CDATA[Your Platform Already Has AI Users]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering</guid>
            <pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[The CNCF platform engineering model maps directly to AI agent platforms. Same dimensions, different bottlenecks.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Your Platform Already Has AI Users" src="https://frosty-babbage-3125a3.netlify.app/assets/images/01-platform-two-consumers-77260891fef292cd74a0256076849f87.png" width="2883" height="522" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>Platform engineering matured around one thesis: reduce cognitive load for developers. The <a href="https://tag-app-delivery.cncf.io/whitepapers/platforms/" target="_blank" rel="noopener noreferrer" class="">CNCF Platforms White Paper</a> codified the pattern. Internal developer portals, golden paths, self-service infrastructure — all designed so human teams ship faster without drowning in complexity.</p>
<p>AI agents now consume the same infrastructure. They clone repos, call CI/CD APIs, create pull requests, query issue trackers. But they don't read your Confluence wiki or file support tickets. They parse tool schemas and call structured APIs.</p>
<p>The bottlenecks shift. Humans hit cognitive overload: too many tools, tribal knowledge, context switching. Agents hit context window limits: too many tokens, irrelevant information, unstructured access. Same problem class, different constraint.</p>
<p>The CNCF platform engineering model transfers directly. Here's the mapping, dimension by dimension.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-consumers-one-platform">Two Consumers, One Platform<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering#two-consumers-one-platform" class="hash-link" aria-label="Direct link to Two Consumers, One Platform" title="Direct link to Two Consumers, One Platform" translate="no">​</a></h2>
<p>The CNCF Platforms White Paper defines platform engineering across dimensions like self-service, golden paths, documentation, and guardrails. Each dimension has a direct analog for AI agents.</p>
<table><thead><tr><th>Dimension</th><th>Human Teams</th><th>AI Agents</th></tr></thead><tbody><tr><td><strong>Self-service goal</strong></td><td>Reduce cognitive load, eliminate tickets</td><td>Reduce context window waste, eliminate ambiguity</td></tr><tr><td><strong>Interface</strong></td><td>Portal, CLI, UI, docs</td><td>MCP tools, APIs, structured schemas, tool descriptions</td></tr><tr><td><strong>Golden paths</strong></td><td>Opinionated templates, scaffolding, starter kits</td><td>Skills, agent prompts, workflow definitions, pre-built tool chains</td></tr><tr><td><strong>Discovery</strong></td><td>Service catalog, search, docs site</td><td>Tool registries, ToolSearch, capability advertisement</td></tr><tr><td><strong>Documentation</strong></td><td>How-tos, runbooks, READMEs</td><td>Structured context (CLAUDE.md), tool schemas, few-shot examples in descriptions</td></tr><tr><td><strong>Cognitive load</strong></td><td>Too many choices, context switching, tribal knowledge</td><td>Context window limits, token budgets, irrelevant context noise</td></tr><tr><td><strong>Guardrails</strong></td><td>RBAC, policies, compliance gates</td><td>Sandboxing, permission boundaries, tool allowlists, destructive action blocks</td></tr><tr><td><strong>Toil</strong></td><td>Manual deploys, ticket queues, config drift</td><td>Re-discovering context, re-reading files, redundant searches, hallucinated paths</td></tr><tr><td><strong>Onboarding</strong></td><td>Orientation docs, buddy system, ramp-up period</td><td>System prompts, CLAUDE.md, agent norms, skill loading</td></tr><tr><td><strong>Collaboration</strong></td><td>Slack, PRs, pair programming</td><td>Agent channels, MCP messaging, shared memory stores, handoff protocols</td></tr><tr><td><strong>Scaling</strong></td><td>Hire more people, cross-train</td><td>Spawn more agents, share skills, parallelize</td></tr><tr><td><strong>Failure mode</strong></td><td>Burnout, knowledge silos, bus factor</td><td>Context overflow, stale memory, tool misuse, cascading bad decisions</td></tr><tr><td><strong>Cost model</strong></td><td>Salaries, time, opportunity cost</td><td>Tokens, API calls, compute time, context window budget</td></tr></tbody></table>
<p>The mapping isn't forced. These dimensions emerged because platform engineering solves a universal problem: making infrastructure accessible to its consumers. The consumer type changes the interface, not the architecture.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="where-it-gets-concrete">Where It Gets Concrete<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering#where-it-gets-concrete" class="hash-link" aria-label="Direct link to Where It Gets Concrete" title="Direct link to Where It Gets Concrete" translate="no">​</a></h2>
<p>Three dimensions show the mapping most clearly.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="self-service-from-portals-to-tool-registries">Self-service: from portals to tool registries<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering#self-service-from-portals-to-tool-registries" class="hash-link" aria-label="Direct link to Self-service: from portals to tool registries" title="Direct link to Self-service: from portals to tool registries" translate="no">​</a></h3>
<p>For humans, self-service means a portal where you click "Create Kubernetes Cluster" and get one in minutes. No tickets, no waiting.</p>
<p>For agents, self-service means discoverable tools with structured schemas. An agent calls <code>ToolSearch("kubernetes cluster")</code> and gets back a <code>create_cluster</code> function with typed parameters. No parsing documentation, no guessing at CLI flags.</p>
<p>The same backend serves both. The portal and the tool schema are two interfaces to the same provisioning API.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="golden-paths-from-templates-to-skills">Golden paths: from templates to skills<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering#golden-paths-from-templates-to-skills" class="hash-link" aria-label="Direct link to Golden paths: from templates to skills" title="Direct link to Golden paths: from templates to skills" translate="no">​</a></h3>
<p>Human golden paths are opinionated templates. <code>create-react-app</code> scaffolds a project. A Backstage template wires up CI/CD, monitoring, and deployment in one click.</p>
<p>Agent golden paths are skills — packaged workflows with clear inputs and outputs. A <code>deploy-service</code> skill encapsulates the same decisions the template made: which registry, which pipeline, which environment. The agent composes skills instead of following a wizard.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="documentation-from-readmes-to-structured-context">Documentation: from READMEs to structured context<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering#documentation-from-readmes-to-structured-context" class="hash-link" aria-label="Direct link to Documentation: from READMEs to structured context" title="Direct link to Documentation: from READMEs to structured context" translate="no">​</a></h3>
<p>Humans read READMEs, runbooks, and how-to guides. The documentation is narrative, written for comprehension.</p>
<p>Agents consume structured context: <code>CLAUDE.md</code> files with project conventions, tool schemas with parameter descriptions, few-shot examples embedded in tool descriptions. Narrative documentation wastes context window. Structured context is information-dense.</p>
<p>A platform that serves both invests in both formats — or better, generates one from the other.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-dimensions-the-cncf-model-missed">Three Dimensions the CNCF Model Missed<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering#three-dimensions-the-cncf-model-missed" class="hash-link" aria-label="Direct link to Three Dimensions the CNCF Model Missed" title="Direct link to Three Dimensions the CNCF Model Missed" translate="no">​</a></h2>
<p>The original model doesn't cover three concerns that matter only (or primarily) for AI agents.</p>
<p><strong>Context management.</strong> Humans manage context through experience. An SRE who's been on-call for a year knows which alerts matter. That context is implicit and essentially free.</p>
<p>Agents have explicit context budgets. Every file read, every search result, every system prompt consumes tokens from a finite window. A platform at the "Provisional" level dumps entire files into the prompt. At "Optimizing," agents request exactly what they need and the platform delivers it pre-filtered.</p>
<p>Context management is the single highest-leverage investment for an AI agent platform. Bad context means wasted tokens, slower responses, and worse decisions.</p>
<p><strong>Memory.</strong> Humans accumulate knowledge over time. Institutional memory lives in people's heads, in Slack history, in wikis.</p>
<p>Agents start fresh every session unless the platform provides memory infrastructure. At the "Provisional" level, there's no persistence — every session rediscovers the same project structure. At "Operational," sessions get annotated and handed off manually. At "Optimizing," agents extract insights automatically and share them across a federated knowledge store.</p>
<p><strong>Authority.</strong> Human trust is earned through track record, code review, and organizational culture. A senior engineer merges without approval. A junior gets two reviewers.</p>
<p>Agent authority requires explicit boundaries. Permission systems define which tools an agent can call, which resources it can modify, which actions require human approval. At maturity, these boundaries become dynamic — an agent with a strong track record gets expanded permissions automatically.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-combined-maturity-model">The Combined Maturity Model<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering#the-combined-maturity-model" class="hash-link" aria-label="Direct link to The Combined Maturity Model" title="Direct link to The Combined Maturity Model" translate="no">​</a></h2>
<p>The <a href="https://tag-app-delivery.cncf.io/whitepapers/platform-eng-maturity-model/" target="_blank" rel="noopener noreferrer" class="">CNCF Platform Engineering Maturity Model</a> defines four levels: Provisional, Operational, Scalable, and Optimizing. Each level applies to both human and AI platform consumers. The table below shows the human level (top row) and AI agent equivalent (bottom row) for each dimension.</p>
<table><thead><tr><th>Dimension</th><th></th><th>Provisional</th><th>Operational</th><th>Scalable</th><th>Optimizing</th></tr></thead><tbody><tr><td><strong>Investment</strong></td><td>Human</td><td>Voluntary, temporary</td><td>Dedicated team</td><td>Platform as product</td><td>Enabled ecosystem</td></tr><tr><td></td><td>AI</td><td>Ad-hoc API keys, manual setup</td><td>Dedicated agent infra, budgeted tokens</td><td>Agent platform as product, ROI per task</td><td>Self-provisioning agent ecosystem</td></tr><tr><td><strong>Adoption</strong></td><td>Human</td><td>Erratic</td><td>Extrinsic push</td><td>Intrinsic pull</td><td>Participatory</td></tr><tr><td></td><td>AI</td><td>Hardcoded tool lists</td><td>Tool registries, skill loading</td><td>Self-discovery, capability advertisement</td><td>Agents contribute tools back</td></tr><tr><td><strong>Interfaces</strong></td><td>Human</td><td>Custom processes</td><td>Standard tooling</td><td>Self-service</td><td>Integrated services</td></tr><tr><td></td><td>AI</td><td>Raw CLI, unstructured prompts</td><td>MCP tools, structured schemas</td><td>Self-service composition, skill chaining</td><td>Multi-agent orchestration</td></tr><tr><td><strong>Operations</strong></td><td>Human</td><td>By request</td><td>Centrally tracked</td><td>Centrally enabled</td><td>Managed services</td></tr><tr><td></td><td>AI</td><td>One-off scripts</td><td>Centrally managed skills</td><td>Automated curation, health checks</td><td>Agents maintain own platform</td></tr><tr><td><strong>Measurement</strong></td><td>Human</td><td>Ad hoc</td><td>Consistent collection</td><td>Insights</td><td>Quantitative and qualitative</td></tr><tr><td></td><td>AI</td><td>Manual log review</td><td>Success/failure tracking</td><td>Automated pattern detection</td><td>Agents optimize agents</td></tr><tr><td><strong>Context</strong></td><td>AI</td><td>Entire files in prompt</td><td>Curated system prompts</td><td>Dynamic context injection</td><td>Agents request what they need</td></tr><tr><td><strong>Memory</strong></td><td>AI</td><td>No persistence</td><td>Session annotations</td><td>Structured long-term memory</td><td>Automatic extraction, federated knowledge</td></tr><tr><td><strong>Authority</strong></td><td>AI</td><td>No boundaries</td><td>Static allowlists</td><td>Delegated authority with escalation</td><td>Dynamic trust based on track record</td></tr></tbody></table>
<p>The key insight: most organizations are at different levels for human and AI consumers. A platform might be "Scalable" for developers — self-service portal, product mindset, dedicated team — but "Provisional" for agents — ad-hoc API keys, unstructured prompts, no memory.</p>
<p>Knowing where you stand on both axes tells you where to invest.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-platform-teams-should-do">What Platform Teams Should Do<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/23/ai-agent-platform-engineering#what-platform-teams-should-do" class="hash-link" aria-label="Direct link to What Platform Teams Should Do" title="Direct link to What Platform Teams Should Do" translate="no">​</a></h2>
<p><strong>Audit your AI agent maturity.</strong> Walk through each dimension. Where are you Provisional? Most teams discover they have no structured context strategy and no memory infrastructure.</p>
<p><strong>Invest in structured interfaces first.</strong> MCP tool schemas, typed APIs, and discoverable registries are the highest-ROI investment. They make your existing platform accessible to agents without rebuilding anything.</p>
<p><strong>Treat context as a budget.</strong> Every token an agent spends reading irrelevant information is waste. Create <code>CLAUDE.md</code> files for your repositories. Write tool descriptions that include parameters and examples. Trim your system prompts.</p>
<p><strong>Build memory infrastructure.</strong> Agents that start fresh every session repeat the same discovery work. Even simple persistence — session notes, project context files — eliminates massive redundancy.</p>
<p><strong>Don't build a separate AI platform.</strong> The same APIs, the same CI/CD, the same infrastructure serves both audiences. What changes is the interface layer. Add tool schemas alongside your portal. Add structured context alongside your documentation.</p>
<p>The CNCF got the model right. The platform serves its consumers. Now it has two kinds.</p>]]></content:encoded>
            <category>platform-engineering</category>
            <category>ai-agents</category>
            <category>kubernetes</category>
            <category>CNCF</category>
            <category>MCP</category>
        </item>
        <item>
            <title><![CDATA[Give Your AI Agents a Brain That Survives a Reboot]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab</guid>
            <pubDate>Sun, 22 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Redis Agent Memory Server gives AI agents persistent memory backed by semantic search. Here's how to run it on a homelab Kubernetes cluster with Ollama for zero-cost local inference.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Agent Memory Architecture" src="https://frosty-babbage-3125a3.netlify.app/assets/images/agent-memory-architecture-1b63250244ca49b2a1a496f75e25db1c.png" width="3772" height="1652" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>AI agents learn things about you as you work. Your coding style, your infrastructure quirks, which tools you prefer, how you name things. That context accumulates across dozens of sessions. But it evaporates when the session ends or when you switch tools. Use Claude Code in the terminal, Cursor in the IDE, and a web-based MCP client on your phone. Three tools, three isolated memory silos. You can share static knowledge with files. But the context that accumulates from your interactions has nowhere to go.</p>
<p>What if the memory layer was separate from the tools entirely? <a href="https://github.com/redis/agent-memory-server" target="_blank" rel="noopener noreferrer" class="">Redis Agent Memory Server</a> does exactly that. It's an open-source memory service that speaks <a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">MCP</a> over HTTP. CLI tools (Claude Code, Codex, Gemini CLI), IDE tools (Cursor, Windsurf, Copilot), and any web-based MCP client all connect to the same store. Because the server runs over HTTP, you can expose it beyond the homelab with Cloudflare Tunnels or similar, so tools outside your network connect too. The memory lives on your infrastructure, not theirs.</p>
<p>The server provides two tiers: working memory (session-scoped context that auto-promotes to long-term) and long-term memory (persistent, semantically searchable). It exposes both a REST API and an MCP interface.</p>
<p>I deployed it on my homelab Kubernetes cluster. Four pods, one PVC, zero API costs. Here's how.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>Four components, each a separate Kubernetes deployment. Here's what happens under the hood.</p>
<p><strong>Storing.</strong> When you tell it to remember something, it converts your text into a list of numbers (an embedding). Redis stores those numbers alongside the raw text. The memory is searchable immediately.</p>
<p><strong>Retrieving.</strong> When a tool queries memory, your query becomes numbers too. Redis finds stored memories with the closest numbers, by meaning, not by keyword. An AI tool asking "what do I know about deployment patterns?" finds a memory you stored as "we always use Recreate strategy for PVC-backed workloads" even though the words don't overlap. Unlike wikis or Obsidian vaults where you organize content yourself and search by keyword, here you store unstructured text and the system handles the rest.</p>
<p><strong>Enriching.</strong> In the background, a worker asks a small local LLM "what topics are in this text?" and tags each memory with structured metadata. Combined with topic and entity tags, you get precise recall without writing structured queries. Think of it as auto-filing.</p>
<!-- -->
<table><thead><tr><th>Component</th><th>Image</th><th>Purpose</th></tr></thead><tbody><tr><td>Redis Stack</td><td><code>redis/redis-stack-server:7.4.0-v8</code></td><td>Vector store + pub/sub + persistence</td></tr><tr><td>API Server</td><td><code>piotrzan/agent-memory-server:0.13.2-custom-v4</code></td><td>REST API on port 8000</td></tr><tr><td>MCP Server</td><td>Same custom image</td><td>MCP interface on port 9000</td></tr><tr><td>Task Worker</td><td>Same custom image</td><td>Async processing (embeddings, NER, topic extraction)</td></tr></tbody></table>
<p>The worker handles the async enrichment. Here's the full lifecycle:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="memory-lifecycle">Memory Lifecycle<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#memory-lifecycle" class="hash-link" aria-label="Direct link to Memory Lifecycle" title="Direct link to Memory Lifecycle" translate="no">​</a></h3>
<p>Storing a memory happens in two phases:</p>
<!-- -->
<p>Phase 1 is synchronous. nomic-embed-text converts your text into a 768-dimension vector. Redis stores both the vector and the raw text immediately, so you can search for it right away.</p>
<p>Phase 2 runs in the background. The worker pulls async tasks from Redis (using <a href="https://github.com/chrisguidry/docket" target="_blank" rel="noopener noreferrer" class="">docket</a>), sends text to llama3.2:3b for topic extraction and NER, and writes structured metadata back. This makes future searches more precise; you can filter by topic or entity, not just vector similarity.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-not-just-use-openai-backed-memory">Why Not Just Use OpenAI-backed Memory?<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#why-not-just-use-openai-backed-memory" class="hash-link" aria-label="Direct link to Why Not Just Use OpenAI-backed Memory?" title="Direct link to Why Not Just Use OpenAI-backed Memory?" translate="no">​</a></h3>
<p>Most AI memory solutions (<a href="https://mem0.ai/" target="_blank" rel="noopener noreferrer" class="">Mem0</a>, <a href="https://www.getzep.com/" target="_blank" rel="noopener noreferrer" class="">Zep</a>) require OpenAI or another cloud LLM for embeddings and extraction. Every memory stored costs API tokens. Every semantic search costs more tokens.</p>
<p>Redis Agent Memory Server supports <a href="https://docs.litellm.ai/" target="_blank" rel="noopener noreferrer" class="">LiteLLM</a>, which means it works with 100+ providers. Including Ollama. Running locally. For free.</p>
<div class="info-card-grid" style="grid-template-columns:repeat(2, 1fr)"><div class="info-card"><div class="info-card-header">Embeddings</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Cloud<!-- -->:</strong> <span>OpenAI ada-002 ($0.10/1M tokens)</span></div><div><strong class="text-gray-700 dark:text-gray-400">Homelab<!-- -->:</strong> <span>Ollama nomic-embed-text (free)</span></div></div></div><div class="info-card"><div class="info-card-header">Generation</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Cloud<!-- -->:</strong> <span>GPT-4 for extraction ($30/1M tokens)</span></div><div><strong class="text-gray-700 dark:text-gray-400">Homelab<!-- -->:</strong> <span>Ollama llama3.2:3b (free)</span></div></div></div><div class="info-card"><div class="info-card-header">Vector Store</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Cloud<!-- -->:</strong> <span>Pinecone/Weaviate (managed)</span></div><div><strong class="text-gray-700 dark:text-gray-400">Homelab<!-- -->:</strong> <span>Redis Stack RediSearch (self-hosted)</span></div></div></div><div class="info-card"><div class="info-card-header">Data Location</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Cloud<!-- -->:</strong> <span>Third-party servers</span></div><div><strong class="text-gray-700 dark:text-gray-400">Homelab<!-- -->:</strong> <span>Your cluster, your PVC</span></div></div></div></div>
<p>With Ollama on a spare Mac and Redis on Kubernetes, the entire memory pipeline runs locally. Memories never leave the network.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploying-to-kubernetes">Deploying to Kubernetes<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#deploying-to-kubernetes" class="hash-link" aria-label="Direct link to Deploying to Kubernetes" title="Direct link to Deploying to Kubernetes" translate="no">​</a></h2>
<p>Everything below is the actual deployment. If you just wanted the concept, you can stop here. If you want to run this on your own cluster, keep going.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h3>
<ul>
<li class=""><strong>A Kubernetes cluster.</strong> Any distro works: k3s on a single node, kubeadm, Talos, managed. If you don't have one yet, <a href="https://k3s.io/" target="_blank" rel="noopener noreferrer" class="">k3s</a> gets you there in one command</li>
<li class="">A machine running <a href="https://ollama.com/" target="_blank" rel="noopener noreferrer" class="">Ollama</a> with <code>nomic-embed-text</code> and <code>llama3.2:3b</code> pulled. Can be the same node, a spare laptop, a Mac. Needs ~4GB RAM for both models</li>
<li class="">~2.3GB cluster RAM at pod limits for the 4 deployments (actual usage is lower)</li>
<li class="">A storage class that supports PVCs (Longhorn, local-path, OpenEBS, anything)</li>
<li class="">Optional: ArgoCD for GitOps sync, or just <code>kubectl apply</code></li>
</ul>
<p>Everything lives in the <code>ai-tools</code> namespace. ArgoCD syncs from Git.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="redis-stack">Redis Stack<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#redis-stack" class="hash-link" aria-label="Direct link to Redis Stack" title="Direct link to Redis Stack" translate="no">​</a></h3>
<p>Redis is doing triple duty here: vector index (RediSearch for semantic search), task queue (docket polls it for async worker jobs), and persistent store (AOF + PVC for the actual memories). One pod instead of running Postgres + Pinecone + RabbitMQ separately. Same ops patterns as any other stateful workload: PVC, AOF persistence, health checks. Redis Stack bundles RediSearch (vector similarity) and RedisJSON. The <code>appendonly</code> flag enables AOF persistence, so data survives pod restarts. <code>allkeys-lru</code> eviction keeps memory bounded at 512MB. Oldest entries get evicted when full.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> apps/v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Deployment</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">redis</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">replicas</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">strategy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Recreate  </span><span class="token comment" style="color:rgb(98, 114, 164)"># avoid two pods fighting over PVC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">selector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">matchLabels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">app.kubernetes.io/name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">redis</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">template</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">labels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">app.kubernetes.io/name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">redis</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">app.kubernetes.io/component</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> redis</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">containers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> redis</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> redis/redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">stack</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">server</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">7.4.0</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">v8</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> REDIS_ARGS</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"--appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">containerPort</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">6379</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> redis</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">volumeMounts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">mountPath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> /data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">resources</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">limits</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 500m</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 768Mi</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">requests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 100m</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 256Mi</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">livenessProbe</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">exec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">command</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"redis-cli"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ping"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">initialDelaySeconds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">15</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">periodSeconds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">readinessProbe</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">exec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">command</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"redis-cli"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"ping"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">initialDelaySeconds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">5</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">periodSeconds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">10</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">persistentVolumeClaim</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">claimName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> PersistentVolumeClaim</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">accessModes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> ReadWriteOnce</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">storageClassName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> longhorn</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">resources</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">requests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 2Gi</span><br></span></code></pre></div></div>
<p><code>Recreate</code> strategy is important because two Redis instances can't share the same PVC.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-application-pods">The Application Pods<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#the-application-pods" class="hash-link" aria-label="Direct link to The Application Pods" title="Direct link to The Application Pods" translate="no">​</a></h3>
<p>All three application components share environment variables. The key ones:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">env</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> REDIS_URL</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> redis</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">//agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">redis.ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools.svc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">6379</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> LONG_TERM_MEMORY</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"true"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ENABLE_TOPIC_EXTRACTION</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"true"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ENABLE_NER</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"true"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> GENERATION_MODEL</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ollama/llama3.2</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">3b    </span><span class="token comment" style="color:rgb(98, 114, 164)"># explicit tag matters!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> EMBEDDING_MODEL</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ollama/nomic</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">embed</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">text</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> OLLAMA_API_BASE</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> http</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">//ollama.ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools.svc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token number">11434</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># point at your Ollama service</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> REDISVL_VECTOR_DIMENSIONS</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"768"</span><span class="token plain">                  </span><span class="token comment" style="color:rgb(98, 114, 164)"># nomic-embed-text output size</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> DISABLE_AUTH</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">value</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"true"</span><span class="token plain">                 </span><span class="token comment" style="color:rgb(98, 114, 164)"># homelab, internal network</span><br></span></code></pre></div></div>
<p>Each component gets a different command:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># API server (default entrypoint)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># No command override needed -- runs uvicorn on port 8000</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># MCP server</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">command</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"agent-memory"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"mcp"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"--mode"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"streamable-http"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Task worker</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">command</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"agent-memory"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"task-worker"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><br></span></code></pre></div></div>
<p>The API server handles REST requests. The MCP server speaks Model Context Protocol over streamable-http. The worker runs docket-based async tasks.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="exposing-ollama-to-the-cluster">Exposing Ollama to the Cluster<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#exposing-ollama-to-the-cluster" class="hash-link" aria-label="Direct link to Exposing Ollama to the Cluster" title="Direct link to Exposing Ollama to the Cluster" translate="no">​</a></h3>
<p>Ollama can run on any machine on your network: the cluster node itself, a spare laptop, a desktop. If it runs outside the cluster, you need a headless Service with manual Endpoints pointing at whatever IP runs Ollama:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Service</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ollama</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">clusterIP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> None</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">11434</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">targetPort</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">11434</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> http</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">---</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Endpoints</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ollama</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">subsets</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">addresses</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">ip</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> &lt;OLLAMA_HOST_IP</span><span class="token punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">11434</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> http</span><br></span></code></pre></div></div>
<p>Now any pod in the cluster can reach Ollama at <code>ollama.ai-tools.svc:11434</code>. If Ollama runs on a cluster node, you can skip the Endpoints and use a regular Service or just point <code>OLLAMA_API_BASE</code> at the node IP directly.</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>My setup</div><div class="admonitionContent_BuS1"><p>In my homelab, Ollama runs on a Mac on the local network. The Service is named <code>ollama-mac</code> with an <code>availability: "ephemeral"</code> annotation as documentation. When the Mac sleeps, the worker's Ollama calls fail and tasks retry when it wakes up.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="adding-streamable-http-support">Adding Streamable HTTP Support<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#adding-streamable-http-support" class="hash-link" aria-label="Direct link to Adding Streamable HTTP Support" title="Direct link to Adding Streamable HTTP Support" translate="no">​</a></h2>
<p>Upstream agent-memory-server supports <code>stdio</code> and <code>sse</code> transport modes for MCP. Neither works well for a Kubernetes deployment where Claude Code connects over HTTP.</p>
<p><code>stdio</code> requires a local process. <code>sse</code> uses server-sent events but doesn't support the streamable-http transport that Claude Code's HTTP MCP client expects.</p>
<p>A small patch fixes this. Three changes:</p>
<ol>
<li class="">Add <code>streamable-http</code> as a valid <code>--mode</code> choice in the CLI</li>
<li class="">Set <code>stateless_http=True</code> so Claude Code subagents (which don't complete the MCP initialization handshake) still work</li>
<li class="">Return <code>Response()</code> instead of <code>None</code> to prevent a TypeError when SSE clients disconnect</li>
</ol>
<div class="language-diff codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-diff codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># cli.py - add streamable-http mode</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-    type=click.Choice(["stdio", "sse"]),</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">+    type=click.Choice(["stdio", "sse", "streamable-http"]),</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># cli.py - handle the new mode</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">+        elif mode == "streamable-http":</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">+            await mcp_app.run_streamable_http_async()</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># mcp.py - stateless mode for subagents</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">+        kwargs.setdefault("stateless_http", True)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># mcp.py - fix SSE disconnect crash</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">+            return Response()</span><br></span></code></pre></div></div>
<p>The build script clones upstream, applies the patch, and builds:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> clone </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--depth</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"> https://github.com/redis/agent-memory-server.git </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$BUILD_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">cd</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$BUILD_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">git</span><span class="token plain"> apply mcp-patches.patch</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> build </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> Dockerfile.patched </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--target</span><span class="token plain"> standard </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$IMAGE</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><br></span></code></pre></div></div>
<p>The resulting image (<code>piotrzan/agent-memory-server:0.13.2-custom-v4</code>) is used by all three application pods. I've submitted a PR upstream to add streamable-http support natively, but until that lands you'll need a custom image.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="connecting-mcp-clients">Connecting MCP Clients<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#connecting-mcp-clients" class="hash-link" aria-label="Direct link to Connecting MCP Clients" title="Direct link to Connecting MCP Clients" translate="no">​</a></h2>
<p>The MCP server pod exposes port 9000. How you make it reachable depends on your setup: a NodePort, an Ingress, Gateway API, a LoadBalancer, or even <code>kubectl port-forward</code> for testing. The only requirement is that your MCP client can reach the <code>/mcp</code> endpoint over HTTP.</p>
<p>Any MCP client that supports HTTP transport can connect. The configuration is the same pattern across tools:</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"mcpServers"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"agent-memory"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"http"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"url"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"http://&lt;your-mcp-host&gt;/mcp"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>Replace <code>&lt;your-mcp-host&gt;</code> with whatever hostname or IP reaches your MCP service. That's it. Any MCP-capable tool (Claude Code, Cursor, Windsurf, Codex) now has persistent memory across all sessions on any machine that can reach the endpoint.</p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>My setup</div><div class="admonitionContent_BuS1"><p>I expose the MCP server through Envoy Gateway with an HTTPRoute:</p><div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> gateway.networking.k8s.io/v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> HTTPRoute</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">parentRefs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> homelab</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">gateway</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> envoy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">gateway</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">system</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">sectionName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> http</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">hostnames</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"agent-memory.homelab.local"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">rules</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">backendRefs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> agent</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">mcp</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ai</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">tools</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">9000</span><br></span></code></pre></div></div><p>Gateway API requires a <code>ReferenceGrant</code> in the target namespace when an HTTPRoute references a backend Service in a different namespace. Without it, the route silently fails.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-you-get">What You Get<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#what-you-get" class="hash-link" aria-label="Direct link to What You Get" title="Direct link to What You Get" translate="no">​</a></h3>
<p>Once connected, any MCP client gains these tools:</p>
<ul>
<li class=""><code>create_long_term_memories</code> -- store facts, preferences, patterns</li>
<li class=""><code>search_long_term_memory</code> -- semantic search across all stored memories</li>
<li class=""><code>set_working_memory</code> -- session-scoped context that auto-promotes to long-term</li>
<li class=""><code>get_working_memory</code> -- recall current session state</li>
<li class=""><code>memory_prompt</code> -- get a context-enriched prompt with relevant memories injected</li>
</ul>
<p>Say "remember that I always use bun instead of npm" and it persists. Next session, on a different machine, in a different tool, the memory is there. The semantic search means it finds relevant memories even with different wording.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-matters">Why This Matters<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#why-this-matters" class="hash-link" aria-label="Direct link to Why This Matters" title="Direct link to Why This Matters" translate="no">​</a></h2>
<p>This setup solves a real problem: AI tools are stateless by default. Every session is a blank slate. Memory servers fix that, but most require cloud APIs that cost money and store your data on their infrastructure.</p>
<p>Think about what AI memories contain: details about your codebase, your infrastructure topology, your workflow habits, your credentials naming patterns. With cloud memory services, that data lives in someone else's database, governed by their retention policies. With this setup, the canonical store is yours. Redis on your PVC, embeddings computed by Ollama on your hardware, search indexes you control. When an MCP client retrieves a memory and sends it to Claude or Codex for inference, that's your choice for that interaction. The difference is who holds the source of truth. You decide what gets stored, what gets shared, and what gets deleted.</p>
<p>Running agent-memory-server on Kubernetes with Ollama gives you:</p>
<ul>
<li class="">You own the data -- memories live in your database, governed by your retention policies</li>
<li class="">Zero inference costs -- Ollama on a spare Mac handles all embeddings and generation</li>
<li class="">Kubernetes ops tooling -- health checks, rolling updates, PVC persistence, ArgoCD GitOps</li>
<li class="">Multi-machine memory -- any device pointing at the MCP endpoint shares the same memory store</li>
</ul>
<p>The stack is ~2.3GB RAM at limits (Redis 768Mi + three app pods at 512Mi each). Actual usage sits well below that; requests total ~640Mi (Redis 256Mi + 3x128Mi).</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="links">Links<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/22/agent-memory-server-homelab#links" class="hash-link" aria-label="Direct link to Links" title="Direct link to Links" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://github.com/redis/agent-memory-server" target="_blank" rel="noopener noreferrer" class="">Redis Agent Memory Server</a> -- upstream project</li>
<li class=""><a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">Model Context Protocol</a> -- the MCP specification</li>
<li class=""><a href="https://ollama.com/" target="_blank" rel="noopener noreferrer" class="">Ollama</a> -- local LLM inference</li>
<li class=""><a href="https://redis.io/docs/latest/develop/interact/search-and-query/" target="_blank" rel="noopener noreferrer" class="">RediSearch</a> -- vector similarity search in Redis</li>
<li class=""><a href="https://github.com/Piotr1215/homelab" target="_blank" rel="noopener noreferrer" class="">Homelab repo</a> -- full working manifests in <code>gitops/apps/agent-memory-server/</code></li>
</ul>]]></content:encoded>
            <category>homelab</category>
            <category>kubernetes</category>
            <category>ai</category>
            <category>mcp</category>
            <category>redis</category>
            <category>ollama</category>
            <category>gitops</category>
        </item>
        <item>
            <title><![CDATA[What If Your Docker Was Also Your Kubernetes?]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes</guid>
            <pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[vind runs Kubernetes in Docker containers sharing the host daemon. Images work immediately, LoadBalancers get real IPs.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="vind" src="https://frosty-babbage-3125a3.netlify.app/assets/images/vind-hero-18ff5e43ac85328614d71ff804e66e2f.png" width="1792" height="1024" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>Local Kubernetes development has rough edges. kind and minikube make cluster creation trivial, but common workflows need extra setup.</p>
<p>Exposing a LoadBalancer service means configuring MetalLB or tunneling through ngrok. Testing a new image means running <code>kind load docker-image</code> every iteration.</p>
<p>vind encapsulates these workflows. Kubernetes nodes run as Docker containers sharing the host daemon, so images pull straight from the local cache. A built-in LoadBalancer controller assigns real IPs without additional configuration.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vind">vind<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#vind" class="hash-link" aria-label="Direct link to vind" title="Direct link to vind" translate="no">​</a></h2>
<p><a href="https://github.com/loft-sh/vind" target="_blank" rel="noopener noreferrer" class="">vind</a> (vCluster in Docker) runs the Kubernetes control plane and simulated nodes as Docker containers. They share your host's Docker daemon. Currently experimental but functional.</p>
<p><strong>Prerequisites:</strong></p>
<ul>
<li class="">Docker with containerd image store enabled (see below)</li>
<li class="">vCluster CLI v0.31.0+ (<a href="https://www.vcluster.com/docs/vcluster/deploy/basics" target="_blank" rel="noopener noreferrer" class="">install guide</a>)</li>
<li class="">Linux: bridge kernel modules loaded</li>
</ul>
<p><strong>Docker setup</strong> (required for registry proxy):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Enable containerd image store</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'{"features":{"containerd-snapshotter":true}}'</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">tee</span><span class="token plain"> /etc/docker/daemon.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> systemctl restart </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Verify</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> info </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"driver-type"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Should show: driver-type: io.containerd.snapshotter.v1</span><br></span></code></pre></div></div>
<p><strong>Linux kernel modules</strong> (if node join fails):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> modprobe bridge br_netfilter</span><br></span></code></pre></div></div>
<p><strong>vind workflow:</strong></p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># vcluster.yaml - need at least one node for pods to schedule</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">experimental</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">docker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">nodes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"worker-1"</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># 1. Set Docker driver (once)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vcluster use driver </span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 2. Create cluster with node config</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vcluster create my-cluster </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--values</span><span class="token plain"> vcluster.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 3. Deploy (images from your local Docker cache)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl apply </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> deployment.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># LoadBalancer gets an IP</span><br></span></code></pre></div></div>
<p>Your local Docker images are already available. LoadBalancer services get real IPs reachable from your machine.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vcluster-platform-free-tier">vCluster Platform Free Tier<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#vcluster-platform-free-tier" class="hash-link" aria-label="Direct link to vCluster Platform Free Tier" title="Direct link to vCluster Platform Free Tier" translate="no">​</a></h2>
<p>vind works standalone. Connecting it to <a href="https://www.vcluster.com/blog/launching-vcluster-free-get-enterprise-features-at-no-cost" target="_blank" rel="noopener noreferrer" class="">vCluster Platform's free tier</a> adds management features.</p>
<p><strong>Enterprise features now free:</strong></p>
<ul>
<li class="">Sleep mode (pause/resume clusters)</li>
<li class="">Templates and self-service</li>
<li class="">User management and RBAC</li>
<li class="">CRD sync and sync patches</li>
<li class="">Embedded etcd</li>
</ul>
<p><strong>Limits:</strong></p>
<ul>
<li class="">64 vCPU cores</li>
<li class="">32 GPUs</li>
<li class="">Unlimited users</li>
<li class="">Unlimited host clusters</li>
<li class="">Unlimited virtual clusters</li>
</ul>
<p>Covers homelabs, dev environments, CI pipelines, and conference demos.</p>
<p><img decoding="async" loading="lazy" alt="Platform dashboard showing vind cluster" src="https://frosty-babbage-3125a3.netlify.app/assets/images/platform-vind-dashboard-ef1cefe5f04b9755a1e076ee40e089ac.png" width="2422" height="1134" class="img_ev3q"></p>
<p><strong>Activate free tier:</strong></p>
<ol>
<li class="">Deploy vCluster Platform following the <a href="https://www.vcluster.com/docs/platform/install/quick-start-guide" target="_blank" rel="noopener noreferrer" class="">installation guide</a></li>
<li class="">Enter your email when prompted in the Platform UI</li>
<li class="">Complete activation via the email link</li>
</ol>
<p>Once connected, vind clusters appear in the Platform dashboard with options for multi-cluster management, team access sharing, and external node joining.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="registry-proxy">Registry Proxy<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#registry-proxy" class="hash-link" aria-label="Direct link to Registry Proxy" title="Direct link to Registry Proxy" translate="no">​</a></h2>
<p>The registry proxy is why vind iteration is faster than kind. Comparison:</p>
<p><strong>kind:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> build </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><span class="token plain"> myapp:v2 </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain">           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Build locally</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kind load docker-image myapp:v2      </span><span class="token comment" style="color:rgb(98, 114, 164)"># Copy entire image into cluster</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">set</span><span class="token plain"> image deploy/app </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">app</span><span class="token operator">=</span><span class="token plain">myapp:v2  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Deploy</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Wait for image copy... then deploy</span><br></span></code></pre></div></div>
<p><strong>vind:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> build </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><span class="token plain"> myapp:v2 </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain">           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Build locally</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">set</span><span class="token plain"> image deploy/app </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">app</span><span class="token operator">=</span><span class="token plain">myapp:v2  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Deploy immediately</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Pod starts, registry proxy serves image from local Docker</span><br></span></code></pre></div></div>
<p>The registry proxy runs inside vind and intercepts image pulls. When containerd inside the worker node requests <code>myapp:v2</code>, the proxy checks the host Docker daemon first. Local images are served directly.</p>
<p>This also works for public images. Pull <code>nginx:latest</code> once on your host, every vind cluster reuses it.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># On host</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> pull nginx:latest</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># In vind cluster - instant, no network pull</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl run nginx </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--image</span><span class="token operator">=</span><span class="token plain">nginx:latest</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="loadbalancer-services-that-work">LoadBalancer Services That Work<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#loadbalancer-services-that-work" class="hash-link" aria-label="Direct link to LoadBalancer Services That Work" title="Direct link to LoadBalancer Services That Work" translate="no">​</a></h2>
<p>With kind, LoadBalancer services stay <code>&lt;pending&gt;</code> without additional setup. NodePort or port-forward become the standard workarounds.</p>
<p>vind includes a LoadBalancer controller that assigns real IPs:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl create deployment web </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--image</span><span class="token operator">=</span><span class="token plain">nginx</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl expose deployment web </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--port</span><span class="token operator">=</span><span class="token number">80</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--type</span><span class="token operator">=</span><span class="token plain">LoadBalancer</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl get svc web</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># NAME   TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># web    LoadBalancer   10.96.45.123   172.19.0.5    80:31234/TCP</span><br></span></code></pre></div></div>
<p>That IP is routable from your host:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token number">172.19</span><span class="token plain">.0.5</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># &lt;!DOCTYPE html&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># &lt;title&gt;Welcome to nginx!&lt;/title&gt;</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># ...</span><br></span></code></pre></div></div>
<p>Multiple LoadBalancer services get different IPs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="multi-node-clusters">Multi-Node Clusters<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#multi-node-clusters" class="hash-link" aria-label="Direct link to Multi-Node Clusters" title="Direct link to Multi-Node Clusters" translate="no">​</a></h2>
<p>Test node affinity, pod anti-affinity, or DaemonSets with multiple workers:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># vcluster.yaml</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">experimental</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">docker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">nodes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"worker-1"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"worker-2"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"worker-3"</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">vcluster create multi-node </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--values</span><span class="token plain"> vcluster.yaml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl get nodes</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># NAME       STATUS   ROLES    AGE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># worker-1   Ready    &lt;none&gt;   30s</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># worker-2   Ready    &lt;none&gt;   30s</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># worker-3   Ready    &lt;none&gt;   30s</span><br></span></code></pre></div></div>
<p>Each node is a Docker container. Add more by adding lines to the config.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="external-nodes-via-platform">External Nodes via Platform<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#external-nodes-via-platform" class="hash-link" aria-label="Direct link to External Nodes via Platform" title="Direct link to External Nodes via Platform" translate="no">​</a></h2>
<p>With vCluster Platform Pro, you can join real machines to your vind cluster using vCluster VPN. This enables hybrid scenarios: your laptop runs the control plane, a Raspberry Pi or cloud VM joins as a worker.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># vcluster.yaml</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">experimental</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">docker</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">nodes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">node</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">privateNodes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">vpn</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">enabled</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">nodeToNode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">enabled</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">vcluster create hybrid </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--values</span><span class="token plain"> vcluster.yaml</span><br></span></code></pre></div></div>
<p>The external machine connects through the Platform and appears as a node in <code>kubectl get nodes</code>. Test ARM workloads, edge scenarios, or distribute workloads across machines.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vind-vs-kind">vind vs kind<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#vind-vs-kind" class="hash-link" aria-label="Direct link to vind vs kind" title="Direct link to vind vs kind" translate="no">​</a></h2>
<table><thead><tr><th>Feature</th><th>kind</th><th>vind</th></tr></thead><tbody><tr><td>LoadBalancer</td><td>Requires MetalLB setup</td><td>Built-in controller</td></tr><tr><td>Images</td><td><code>kind load</code> per change</td><td>Pull-through from Docker</td></tr><tr><td>Multi-node</td><td>Separate config</td><td>Add lines to yaml</td></tr><tr><td>Pause/resume</td><td>Delete and recreate</td><td><code>vcluster pause/resume</code></td></tr><tr><td>Management UI</td><td>CLI only</td><td>Platform (free tier)</td></tr><tr><td>External nodes</td><td>Local only</td><td>VPN join (Pro)</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="choose-vind-when">Choose vind when<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#choose-vind-when" class="hash-link" aria-label="Direct link to Choose vind when" title="Direct link to Choose vind when" translate="no">​</a></h3>
<p>LoadBalancer services need real IPs without MetalLB. Image iteration is frequent (demos, rapid development). Clusters need pausing to free resources. A management UI would help.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="stick-with-kind-when">Stick with kind when<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#stick-with-kind-when" class="hash-link" aria-label="Direct link to Stick with kind when" title="Direct link to Stick with kind when" translate="no">​</a></h3>
<p>The workflow already works. CI/CD pipelines need maximum compatibility. Minimal dependencies matter more than features.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="resources">Resources<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/02/01/vind-local-kubernetes#resources" class="hash-link" aria-label="Direct link to Resources" title="Direct link to Resources" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://www.youtube.com/watch?v=In8vzpKecLs" target="_blank" rel="noopener noreferrer" class="">vind | vCluster in Docker | Across The Tenancy Spectrum</a> - Video walkthrough</li>
<li class=""><a href="https://github.com/loft-sh/vind" target="_blank" rel="noopener noreferrer" class="">vind GitHub</a></li>
<li class=""><a href="https://www.vcluster.com/docs/vcluster/configure/vcluster-yaml/experimental/docker" target="_blank" rel="noopener noreferrer" class="">vind Documentation</a></li>
<li class=""><a href="https://www.vcluster.com/blog/launching-vcluster-free-get-enterprise-features-at-no-cost" target="_blank" rel="noopener noreferrer" class="">Launching vCluster Free</a> - Free tier announcement</li>
<li class=""><a href="https://www.vcluster.com/docs/platform/install/quick-start-guide" target="_blank" rel="noopener noreferrer" class="">Platform Installation Guide</a></li>
</ul>]]></content:encoded>
            <category>kubernetes</category>
            <category>docker</category>
            <category>vcluster</category>
            <category>vind</category>
            <category>local-development</category>
            <category>vcluster-platform</category>
        </item>
        <item>
            <title><![CDATA[Stop Holding Arrow Keys (Fuzzy Jump to Any Word on Your Command Line)]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation</guid>
            <pubDate>Fri, 09 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Long commands are tedious to edit. Instead of arrow keys or Ctrl+Left repeatedly, fuzzy-search any word and jump straight to it.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="zsh-jumper" src="https://frosty-babbage-3125a3.netlify.app/assets/images/zsh-jumper-hero-7f4eb24fc43635a1841299d4c463c1c2.png" width="1536" height="1024" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>You've typed a 10-word kubectl command. You realize the namespace is wrong. Now you're holding <code>Ctrl+Left</code> six times to get there. Or worse, reaching for the mouse.</p>
<p>Vim solved this for text editing decades ago. Why are we still navigating command lines like it's 1985?</p>
<p><a href="https://github.com/Piotr1215/zsh-jumper" target="_blank" rel="noopener noreferrer" class="">zsh-jumper</a> brings fuzzy search to command line navigation. Press a key, pick a word, cursor jumps there. Or press <code>;</code> and a letter for instant EasyMotion-style jumps.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem">The Problem<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#the-problem" class="hash-link" aria-label="Direct link to The Problem" title="Direct link to The Problem" translate="no">​</a></h2>
<p>Terminal commands grow long:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl get pods </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> kube-system </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--output</span><span class="token plain"> wide </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--selector</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">app</span><span class="token operator">=</span><span class="token plain">nginx</span><br></span></code></pre></div></div>
<p>You need to edit <code>kube-system</code>. Options:</p>
<ol>
<li class=""><strong>Arrow keys</strong> - Hold until you get there. Slow.</li>
<li class=""><strong>Ctrl+Left/Right</strong> - Jump word by word. Better, but still multiple presses.</li>
<li class=""><strong>Home then Ctrl+Right</strong> - Start from beginning. Indirect.</li>
<li class=""><strong>Mouse</strong> - Works, but your hands leave the keyboard.</li>
</ol>
<p>None of these scale. A 15-word command means 15 potential targets.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution">The Solution<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#the-solution" class="hash-link" aria-label="Direct link to The Solution" title="Direct link to The Solution" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">$ kubectl get pods </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> kube-system </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--output</span><span class="token plain"> wide</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    ▲</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">Ctrl+X /</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">→ </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">a</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">kubectl </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">get </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">d</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">pods </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">f</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">-n </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">g</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">kube-system </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">h</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">--output </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">j</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">wide   ← overlay</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">┌─────────────────────────────────────┬────────────────────────────────────────┐</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ jump</span><span class="token operator">&gt;</span><span class="token plain">                               │ kubectl controls the Kubernetes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain">.     │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│ ^S:wrap </span><span class="token operator">|</span><span class="token plain"> ^E:var </span><span class="token operator">|</span><span class="token plain"> ^R:replace </span><span class="token operator">|</span><span class="token plain"> ^M:m│                                        │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│─────────────────────────────────────│                                        │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│</span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain">: kubectl                         │ Basic Commands </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Beginner</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">:             │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│  </span><span class="token number">2</span><span class="token plain">: get                             │   create    Create a resource          │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│  </span><span class="token number">3</span><span class="token plain">: pods                            │   expose    Expose a resource          │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│  </span><span class="token number">4</span><span class="token plain">: </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain">                              │   run       Run a particular image     │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│  </span><span class="token number">5</span><span class="token plain">: kube-system                     │   </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">set</span><span class="token plain">       Set specific features      │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│  </span><span class="token number">6</span><span class="token plain">: </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--output</span><span class="token plain">                        │                                        │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│  </span><span class="token number">7</span><span class="token plain">: wide                            │ Basic Commands </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">Intermediate</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">:         │</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">└─────────────────────────────────────┴────────────────────────────────────────┘</span><br></span></code></pre></div></div>
<p>The overlay shows letter hints (<code>[a]kubectl [s]get [d]pods</code>) for instant jump. The picker has numbered items for fuzzy search, with <code>--help</code> preview on the right.</p>
<p>Type <code>kube</code>, hit enter. Cursor lands at <code>kube-system</code>. Edit, continue.</p>
<p>Or press <code>;</code> then <code>g</code> to jump directly to <code>kube-system</code>. No fuzzy searching needed.</p>
<p>The mental model matches how you think: "I want to change the namespace" not "I need to move left 6 words."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="installation">Installation<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#installation" class="hash-link" aria-label="Direct link to Installation" title="Direct link to Installation" translate="no">​</a></h2>
<p>Requires fzf (or sk, peco, percol). Most of you have fzf already.</p>
<div class="language-zsh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-zsh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># zinit</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">zinit light Piotr1215/zsh-jumper</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># antigen</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">antigen bundle Piotr1215/zsh-jumper</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># oh-my-zsh</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">git clone https://github.com/Piotr1215/zsh-jumper \</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-jumper</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># then add zsh-jumper to plugins in .zshrc</span><br></span></code></pre></div></div>
<p>Default binding: <code>Ctrl+X /</code></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fzf-actions">FZF Actions<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#fzf-actions" class="hash-link" aria-label="Direct link to FZF Actions" title="Direct link to FZF Actions" translate="no">​</a></h2>
<p>Beyond jumping, the picker offers token manipulation:</p>
<table><thead><tr><th>Key</th><th>Action</th></tr></thead><tbody><tr><td><code>Enter</code></td><td>Jump to selected token</td></tr><tr><td><code>Ctrl+S</code></td><td>Wrap token in <code>"..."</code>, <code>'...'</code>, <code>$(...)</code>, etc.</td></tr><tr><td><code>Ctrl+E</code></td><td>Extract token to <code>UPPERCASE</code> variable</td></tr><tr><td><code>Ctrl+R</code></td><td>Replace token (delete and position cursor)</td></tr><tr><td><code>Ctrl+M</code></td><td>Move/swap token with another position</td></tr><tr><td><code>;</code></td><td>Enter instant mode (then press a-z to jump)</td></tr></tbody></table>
<p><strong>Variable extraction</strong> converts <code>my-gpu</code> to:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">MY_GPU</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"my-gpu"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl run </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">test</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--image</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$MY_GPU</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></span></code></pre></div></div>
<p><strong>Instant mode</strong>: Press <code>;</code> then a hint letter to jump directly. The overlay on your command line shows the mapping.</p>
<p><strong>Replace mode</strong> is particularly useful: it deletes the token and leaves cursor in place, so you get full zsh tab completion for the replacement text.</p>
<p>For complex multiline editing, <code>Ctrl+X Ctrl+E</code> (edit in <code>$EDITOR</code>) still shines. zsh-jumper is for quick navigation and token manipulation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="preview-panel">Preview Panel<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#preview-panel" class="hash-link" aria-label="Direct link to Preview Panel" title="Direct link to Preview Panel" translate="no">​</a></h2>
<p>The right panel shows contextual help:</p>
<ul>
<li class=""><strong>Commands</strong>: <code>--help</code> output (with bat highlighting), fallback to <code>tldr</code>, then <code>man</code></li>
<li class=""><strong>Files</strong>: Content preview via <code>bat</code> (or <code>head</code>)</li>
<li class=""><strong>Directories</strong>: <code>ls -la</code> listing</li>
</ul>
<p>This makes the picker double as a quick reference. Forgot what flags kubectl accepts? Just invoke the picker.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-word-numbering">Why Word Numbering?<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#why-word-numbering" class="hash-link" aria-label="Direct link to Why Word Numbering?" title="Direct link to Why Word Numbering?" translate="no">​</a></h2>
<p>The picker shows numbered words:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">1: kubectl</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">2: get</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">3: -n</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">4: --user</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">5: admin</span><br></span></code></pre></div></div>
<p>This prevents ambiguity. If you search for <code>-u</code>, you want <code>-n</code> at position 3, not a substring match inside <code>--user</code>. Numbers ensure exact cursor placement.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuration">Configuration<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#configuration" class="hash-link" aria-label="Direct link to Configuration" title="Direct link to Configuration" translate="no">​</a></h2>
<p>All options via zstyle, set before loading the plugin:</p>
<div class="language-zsh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-zsh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Force specific picker</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">zstyle ':zsh-jumper:' picker fzf</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Custom picker options</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">zstyle ':zsh-jumper:' picker-opts '--height=50% --reverse --border'</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Where cursor lands: start (default), middle, end</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">zstyle ':zsh-jumper:' cursor end</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"># Custom keybinding</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">zstyle ':zsh-jumper:' binding '^J'</span><br></span></code></pre></div></div>
<p>For tmux users: the plugin auto-detects tmux and uses <code>fzf-tmux</code> for a floating popup instead of inline fzf.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vi-mode">Vi Mode<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#vi-mode" class="hash-link" aria-label="Direct link to Vi Mode" title="Direct link to Vi Mode" translate="no">​</a></h2>
<p>If you use vi mode in zsh, bind to both insert and command modes:</p>
<div class="language-zsh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-zsh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">zstyle ':zsh-jumper:' disable-bindings yes</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">bindkey -M viins '^X/' zsh-jumper-widget</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">bindkey -M vicmd '^X/' zsh-jumper-widget</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="workflow-tips">Workflow Tips<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#workflow-tips" class="hash-link" aria-label="Direct link to Workflow Tips" title="Direct link to Workflow Tips" translate="no">​</a></h2>
<p>The built-in actions handle most edits directly:</p>
<ul>
<li class=""><strong>Wrong value?</strong> <code>Ctrl+R</code> to replace with full tab completion</li>
<li class=""><strong>Need quotes?</strong> <code>Ctrl+S</code> to wrap in <code>"..."</code>, <code>'...'</code>, or <code>$(...)</code></li>
<li class=""><strong>Reuse a value?</strong> <code>Ctrl+E</code> to extract to a variable</li>
<li class=""><strong>Wrong order?</strong> <code>Ctrl+M</code> to swap positions</li>
</ul>
<p>For anything else, jump to the word then use standard zsh:</p>
<ul>
<li class=""><code>Alt+D</code> - Delete word forward</li>
<li class=""><code>Ctrl+W</code> - Delete word backward</li>
<li class=""><code>Ctrl+K</code> - Kill to end of line</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="multiline-commands">Multiline Commands<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#multiline-commands" class="hash-link" aria-label="Direct link to Multiline Commands" title="Direct link to Multiline Commands" translate="no">​</a></h2>
<p>Works with backslash continuations:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">docker</span><span class="token plain"> run </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--name</span><span class="token plain"> my-container </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--network</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">host</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    nginx:latest</span><br></span></code></pre></div></div>
<p>Line continuation characters are filtered out. You see only actual command tokens in the picker.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="extensibility">Extensibility<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#extensibility" class="hash-link" aria-label="Direct link to Extensibility" title="Direct link to Extensibility" translate="no">​</a></h2>
<p>For power users, custom actions and previewers via TOML config:</p>
<div class="language-zsh codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-zsh codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">zstyle ':zsh-jumper:' config ~/.config/zsh-jumper/config.toml</span><br></span></code></pre></div></div>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">actions</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">binding</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'ctrl-u'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">description</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'uppercase'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">script</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'~/.config/zsh-jumper/scripts/uppercase.sh'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">previewers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">pattern</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'^https?://'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">description</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'URL preview'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">script</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'~/.config/zsh-jumper/scripts/url-preview.sh'</span><br></span></code></pre></div></div>
<p>Run <code>zsh-jumper-list</code> to see all registered actions and previewers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="performance">Performance<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/09/zsh-jumper-fuzzy-line-navigation#performance" class="hash-link" aria-label="Direct link to Performance" title="Direct link to Performance" translate="no">​</a></h2>
<p>CI measures and enforces thresholds on every commit:</p>
<table><thead><tr><th>Metric</th><th>What it measures</th><th>Threshold</th></tr></thead><tbody><tr><td><strong>Load</strong></td><td>Time to source the plugin</td><td>&lt; 200ms</td></tr><tr><td><strong>Parse</strong></td><td>Tokenize a 10-word command 100×</td><td>&lt; 150ms</td></tr><tr><td><strong>Leak</strong></td><td>Memory delta after 10 load/unload cycles</td><td>&lt; 400KB</td></tr></tbody></table>
<p>Live badges in the <a href="https://github.com/Piotr1215/zsh-jumper" target="_blank" rel="noopener noreferrer" class="">README</a> show actual values from the latest CI run. The plugin loads in under 1ms and cleans up properly on unload.</p>
<hr>
<p><strong>Resources:</strong></p>
<ul>
<li class=""><a href="https://github.com/Piotr1215/zsh-jumper" target="_blank" rel="noopener noreferrer" class="">zsh-jumper on GitHub</a></li>
<li class=""><a href="https://github.com/junegunn/fzf" target="_blank" rel="noopener noreferrer" class="">fzf</a> - Required fuzzy finder</li>
</ul>]]></content:encoded>
            <category>zsh</category>
            <category>terminal</category>
            <category>fzf</category>
            <category>productivity</category>
            <category>cli</category>
        </item>
        <item>
            <title><![CDATA[10 Neovim Features You're Probably Installing Plugins For]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features</guid>
            <pubDate>Sun, 04 Jan 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Multi-cursor editing, shell integration, incremental search preview - these are built into Neovim. No plugins required.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Neovim Built-in Features" src="https://frosty-babbage-3125a3.netlify.app/assets/images/neovim-features-hero-c5a29975639fdb2e01bd2e15caa5985d.png" width="1280" height="720" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>You install a plugin for multi-cursor editing. Another for shell integration. A third for incremental search preview. Your plugin count grows, startup time increases, and you debug compatibility issues between packages.</p>
<p>Most of these features exist in vanilla Neovim. Have existed since Vim 7. Here are 10 built-in features that replace common plugins.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-shell-filter--and-">1. Shell Filter: <code>!</code> and <code>!!</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#1-shell-filter--and-" class="hash-link" aria-label="Direct link to 1-shell-filter--and-" title="Direct link to 1-shell-filter--and-" translate="no">​</a></h2>
<p>Pipe any text through external commands. Every Unix tool becomes a text processor.</p>
<table><thead><tr><th>Command</th><th>Effect</th></tr></thead><tbody><tr><td><code>:.!date</code></td><td>Replace current line with date output</td></tr><tr><td><code>!ip sort</code></td><td>Sort current paragraph</td></tr><tr><td><code>!ap jq .</code></td><td>Format JSON in paragraph</td></tr><tr><td><code>:%!column -t</code></td><td>Align entire file into columns</td></tr></tbody></table>
<p>The motion after <code>!</code> defines the range. <code>ip</code> is "inner paragraph", <code>ap</code> is "around paragraph", <code>%</code> is whole file.</p>
<div class="language-vim codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-vim codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Sort lines 10-20</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:10,20!sort</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Reverse selection (visual mode, then !)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:'&lt;,'&gt;!tac</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Format current function as JSON</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">!i{jq .</span><br></span></code></pre></div></div>
<p>This is the Unix philosophy applied to editing. <code>sort</code>, <code>uniq</code>, <code>jq</code>, <code>column</code>, <code>sed</code> - all available without plugins.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-visual-block-increment-g-ctrl-a">2. Visual Block Increment: <code>g Ctrl-a</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#2-visual-block-increment-g-ctrl-a" class="hash-link" aria-label="Direct link to 2-visual-block-increment-g-ctrl-a" title="Direct link to 2-visual-block-increment-g-ctrl-a" translate="no">​</a></h2>
<p>Select a column of numbers, press <code>g Ctrl-a</code>. They become a sequence.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">item 0          item 1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">item 0    →     item 2</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">item 0          item 3</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">item 0          item 4</span><br></span></code></pre></div></div>
<p>Standard <code>Ctrl-a</code> increments by the same amount. <code>g Ctrl-a</code> creates a sequence where each line increases by one more than the previous.</p>
<p>Works for generating IDs, numbered lists, array indices. No macro needed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-global-command-gpatterncmd">3. Global Command: <code>:g/pattern/cmd</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#3-global-command-gpatterncmd" class="hash-link" aria-label="Direct link to 3-global-command-gpatterncmd" title="Direct link to 3-global-command-gpatterncmd" translate="no">​</a></h2>
<p>Run any Ex command on every line matching a pattern.</p>
<table><thead><tr><th>Command</th><th>Effect</th></tr></thead><tbody><tr><td><code>:g/TODO/d</code></td><td>Delete all lines containing TODO</td></tr><tr><td><code>:g/^$/d</code></td><td>Delete all empty lines</td></tr><tr><td><code>:g/error/t$</code></td><td>Copy lines with "error" to end of file</td></tr><tr><td><code>:g/func/norm A;</code></td><td>Append semicolon to lines containing "func"</td></tr></tbody></table>
<p>The inverse is <code>:v/pattern/cmd</code> - runs on lines NOT matching the pattern.</p>
<div class="language-vim codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-vim codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Delete all comments</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:g/^#/d</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Move all imports to top</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:g/^import/m0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Number all TODO lines</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:let i=1 | g/TODO/s/TODO/\=i.'. TODO'/ | let i+=1</span><br></span></code></pre></div></div>
<p>This is bulk editing without regex gymnastics in the replacement string.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-command-line-registers-ctrl-r">4. Command-line Registers: <code>Ctrl-r</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#4-command-line-registers-ctrl-r" class="hash-link" aria-label="Direct link to 4-command-line-registers-ctrl-r" title="Direct link to 4-command-line-registers-ctrl-r" translate="no">​</a></h2>
<p>In <code>:</code> or <code>/</code> prompts, <code>Ctrl-r</code> inserts register contents. Stop typing long paths and patterns manually.</p>
<table><thead><tr><th>Shortcut</th><th>Inserts</th></tr></thead><tbody><tr><td><code>Ctrl-r Ctrl-w</code></td><td>Word under cursor</td></tr><tr><td><code>Ctrl-r "</code></td><td>Last yanked text</td></tr><tr><td><code>Ctrl-r /</code></td><td>Last search pattern</td></tr><tr><td><code>Ctrl-r =</code></td><td>Expression result</td></tr></tbody></table>
<p>Practical example: cursor on <code>myFunction</code>, want to search for it:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">/Ctrl-r Ctrl-w&lt;Enter&gt;</span><br></span></code></pre></div></div>
<p>Inserts <code>myFunction</code> into the search prompt. Works in substitute commands too:</p>
<div class="language-vim codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-vim codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">:%s/Ctrl-r Ctrl-w/newName/g</span><br></span></code></pre></div></div>
<p>The <code>Ctrl-r =</code> variant evaluates expressions:</p>
<div class="language-vim codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-vim codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">:echo Ctrl-r =system('date')&lt;Enter&gt;</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-normal-on-selection-norm">5. Normal on Selection: <code>:'&lt;,'&gt;norm</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#5-normal-on-selection-norm" class="hash-link" aria-label="Direct link to 5-normal-on-selection-norm" title="Direct link to 5-normal-on-selection-norm" translate="no">​</a></h2>
<p>Run normal mode commands on every selected line. This is multi-cursor without plugins.</p>
<p>Select lines visually, then:</p>
<table><thead><tr><th>Command</th><th>Effect</th></tr></thead><tbody><tr><td><code>:'&lt;,'&gt;norm A,</code></td><td>Append comma to each line</td></tr><tr><td><code>:'&lt;,'&gt;norm I# </code></td><td>Prepend "# " to each line (comment)</td></tr><tr><td><code>:'&lt;,'&gt;norm @q</code></td><td>Run macro q on each line</td></tr><tr><td><code>:'&lt;,'&gt;norm f=lD</code></td><td>Delete everything after = on each line</td></tr></tbody></table>
<p>The <code>norm</code> command executes keystrokes as if typed in normal mode. Combined with a range, it applies to multiple lines.</p>
<div class="language-vim codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-vim codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Convert list to array elements</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Before:      After:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" apple        "apple",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" banana  →    "banana",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" cherry       "cherry",</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:'&lt;,'&gt;norm I"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:'&lt;,'&gt;norm A",</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-the-g-commands">6. The <code>g</code> Commands<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#6-the-g-commands" class="hash-link" aria-label="Direct link to 6-the-g-commands" title="Direct link to 6-the-g-commands" translate="no">​</a></h2>
<p>Navigation jumps you probably don't use:</p>
<table><thead><tr><th>Command</th><th>Effect</th></tr></thead><tbody><tr><td><code>gi</code></td><td>Go to last insert position AND enter insert mode</td></tr><tr><td><code>g;</code></td><td>Jump to previous change location</td></tr><tr><td><code>g,</code></td><td>Jump to next change location</td></tr><tr><td><code>gv</code></td><td>Reselect last visual selection</td></tr></tbody></table>
<p><code>gi</code> is the one I use most. Edit something, scroll away to reference code, <code>gi</code> puts you back exactly where you were typing.</p>
<p><code>g;</code> and <code>g,</code> navigate the change list - every position where you modified text. Different from jump list (<code>Ctrl-o</code>/<code>Ctrl-i</code>) which tracks navigation.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="7-auto-marks">7. Auto-Marks<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#7-auto-marks" class="hash-link" aria-label="Direct link to 7. Auto-Marks" title="Direct link to 7. Auto-Marks" translate="no">​</a></h2>
<p>Vim sets marks automatically. You don't need to remember to set them.</p>
<table><thead><tr><th>Mark</th><th>Position</th></tr></thead><tbody><tr><td><code>``</code></td><td>Previous cursor position (toggle back)</td></tr><tr><td><code>`.</code></td><td>Last change</td></tr><tr><td><code>`"</code></td><td>Position when file was last closed</td></tr><tr><td><code>`[</code> / <code>`]</code></td><td>Start/end of last yank or change</td></tr></tbody></table>
<p>The backtick goes to exact position. Apostrophe goes to line start.</p>
<p><code> </code> <code>(double backtick) is the most useful - it toggles between current position and previous. Edit something, jump to a definition, ```</code> ``` returns you instantly.</p>
<p><code>`[</code> and <code>`]</code> mark the boundaries of your last operation. Useful for reselecting: <code>gv</code> reselects visual, but <code>`[v`]</code> selects the range you just yanked or changed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="8-command-history-window-q">8. Command History Window: <code>q:</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#8-command-history-window-q" class="hash-link" aria-label="Direct link to 8-command-history-window-q" title="Direct link to 8-command-history-window-q" translate="no">​</a></h2>
<p>Press <code>q:</code> and your command history opens in a buffer. Full editing. Search with <code>/</code>. Modify any line. Press Enter to execute.</p>
<table><thead><tr><th>Shortcut</th><th>Opens</th></tr></thead><tbody><tr><td><code>q:</code></td><td>Command history</td></tr><tr><td><code>q/</code></td><td>Search history</td></tr><tr><td><code>Ctrl-f</code></td><td>Switch from cmdline to history window</td></tr></tbody></table>
<p>The third one is key. Start typing a complex command, realize you want to edit it properly, hit <code>Ctrl-f</code>. The command line becomes a buffer.</p>
<p>Compose multi-part commands, edit previous attempts, combine parts from different history entries. Then Enter executes the current line.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="9-live-substitution-preview-inccommand">9. Live Substitution Preview: <code>inccommand</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#9-live-substitution-preview-inccommand" class="hash-link" aria-label="Direct link to 9-live-substitution-preview-inccommand" title="Direct link to 9-live-substitution-preview-inccommand" translate="no">​</a></h2>
<p>See substitution results before executing. Add to your config:</p>
<div class="language-lua codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-lua codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">vim.opt.inccommand = "split"</span><br></span></code></pre></div></div>
<p>Now when you type <code>:%s/old/new/</code>, matches highlight in real-time and a preview window shows what will change.</p>
<p><code>split</code> shows preview in a split window. <code>nosplit</code> highlights inline only.</p>
<p>This catches regex mistakes before they happen. See exactly what <code>:s/foo\(bar\)/\1/</code> will do before pressing Enter.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="10-copymove-lines-t-and-m">10. Copy/Move Lines: <code>:t</code> and <code>:m</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#10-copymove-lines-t-and-m" class="hash-link" aria-label="Direct link to 10-copymove-lines-t-and-m" title="Direct link to 10-copymove-lines-t-and-m" translate="no">​</a></h2>
<p>Duplicate or relocate lines without touching registers. Your yank stays intact.</p>
<table><thead><tr><th>Command</th><th>Effect</th></tr></thead><tbody><tr><td><code>:t.</code></td><td>Duplicate current line below</td></tr><tr><td><code>:t0</code></td><td>Copy current line to top of file</td></tr><tr><td><code>:t$</code></td><td>Copy current line to end of file</td></tr><tr><td><code>:m+2</code></td><td>Move current line 2 lines down</td></tr><tr><td><code>:'&lt;,'&gt;t.</code></td><td>Duplicate selection below cursor</td></tr></tbody></table>
<p><code>:t</code> is copy (think "transfer"), <code>:m</code> is move. Both take a destination line number.</p>
<div class="language-vim codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-vim codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Copy line 5 to after line 10</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:5t10</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Move lines 1-3 to end of file</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:1,3m$</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">" Duplicate current line 5 times</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:t.|t.|t.|t.|t.</span><br></span></code></pre></div></div>
<p>No register pollution. <code>p</code> still pastes what you yanked earlier.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bonus-mark-based-ranges">Bonus: Mark-Based Ranges<a href="https://frosty-babbage-3125a3.netlify.app/blog/2026/01/04/neovim-builtin-features#bonus-mark-based-ranges" class="hash-link" aria-label="Direct link to Bonus: Mark-Based Ranges" title="Direct link to Bonus: Mark-Based Ranges" translate="no">​</a></h2>
<p>Set marks at function boundaries, substitute only within:</p>
<div class="language-vim codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-vim codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">ma          " mark start with 'a'</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">... navigate to end ...</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">mb          " mark end with 'b'</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">:'a,'b s/old/new/g</span><br></span></code></pre></div></div>
<p>Scoped refactoring without selecting anything. The substitution only affects lines between marks a and b.</p>
<hr>
<p><strong>Resources:</strong></p>
<ul>
<li class=""><a href="https://neovim.io/doc/user/" target="_blank" rel="noopener noreferrer" class="">Neovim documentation</a></li>
<li class=""><a href="https://youtu.be/7D1k1l-Q8rA" target="_blank" rel="noopener noreferrer" class="">Video covering these features</a></li>
<li class=""><a href="https://github.com/Piotr1215/dotfiles" target="_blank" rel="noopener noreferrer" class="">My dotfiles</a></li>
</ul>]]></content:encoded>
            <category>neovim</category>
            <category>vim</category>
            <category>productivity</category>
            <category>terminal</category>
            <category>editor</category>
        </item>
        <item>
            <title><![CDATA[Your Shortcuts YAML is Always Outdated (Parse Config Files Instead)]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2025/12/30/dynamic-shortcuts-help-system</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2025/12/30/dynamic-shortcuts-help-system</guid>
            <pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Static shortcuts files go stale within days. Parse your actual config files dynamically with confhelp and use tealdeer for everything else.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Shortcuts Help" src="https://frosty-babbage-3125a3.netlify.app/assets/images/shortcuts-hero-6ccf67c124acc7d50df527a4c9c11156.jpg" width="4928" height="3264" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>You add a new tmux binding. A week later you're hunting through <code>.tmux.conf</code> because you can't remember what you mapped to <code>prefix + g</code>. The shortcuts.yaml you maintain? Three months out of date.</p>
<p>The problem isn't discipline. It's architecture. Any system requiring manual sync between source of truth (config files) and documentation (shortcuts file) will drift.</p>
<p>Parse your config files directly. For workflows that don't map to a single keybinding, use tealdeer custom pages. One popup, two modes, zero maintenance.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-architecture">The Architecture<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/30/dynamic-shortcuts-help-system#the-architecture" class="hash-link" aria-label="Direct link to The Architecture" title="Direct link to The Architecture" translate="no">​</a></h2>
<p>Two types of "help" information exist:</p>
<p><strong>Config-derived</strong> - patterns living in actual config files:</p>
<ul>
<li class="">tmux bindings in <code>.tmux.conf</code></li>
<li class="">zsh bindkeys in <code>.zshrc</code></li>
<li class="">aliases in <code>.zsh_aliases</code></li>
<li class="">abbreviations in <code>.zsh_abbreviations</code></li>
</ul>
<p><strong>Workflow-derived</strong> - multi-step processes and reference material:</p>
<ul>
<li class="">"How do I copy in tmux?" (5 steps, not one keybinding)</li>
<li class="">"What's the taskwarrior syntax?" (cheatsheet)</li>
</ul>
<p>For config-derived, parse the files. For workflow-derived, use tealdeer custom pages.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="confhelp">confhelp<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/30/dynamic-shortcuts-help-system#confhelp" class="hash-link" aria-label="Direct link to confhelp" title="Direct link to confhelp" translate="no">​</a></h2>
<p>I built <a href="https://github.com/Piotr1215/confhelp" target="_blank" rel="noopener noreferrer" class="">confhelp</a> to solve this. Define regex patterns in TOML, point at your config files:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">pip </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> confhelp</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">confhelp </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--init</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># creates ~/.config/confhelp/config.toml</span><br></span></code></pre></div></div>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># ~/.config/confhelp/config.toml</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">tmux</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">paths</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">".tmux.conf"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">match_line</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"^bind"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">regex</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'bind(?:-key)?\s+(?:-n\s+)?(\S+)(.*)'</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">key_group</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">desc_group</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">type</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"tmux"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">truncate</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token number">100</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">alias</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">paths</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">".zsh_aliases"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">".zsh_claude"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">regex</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"alias\\s+(?:-[gs]\\s+)?([^=]+)=(.*)"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">key_group</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">desc_group</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token number">2</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">type</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"alias"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">strip_quotes</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">abbrev</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">paths</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">".zsh_abbreviations"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">mode</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"abbrev_block"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">type</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"abbrev"</span><br></span></code></pre></div></div>
<p>Run it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">confhelp </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-b</span><span class="token plain"> ~/dotfiles            </span><span class="token comment" style="color:rgb(98, 114, 164)"># list all entries</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">confhelp </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-b</span><span class="token plain"> ~/dotfiles </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--select</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># fzf selection, outputs file:line</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">confhelp </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-b</span><span class="token plain"> ~/dotfiles </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--edit</span><span class="token plain">     </span><span class="token comment" style="color:rgb(98, 114, 164)"># fzf selection, opens $EDITOR at line</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">confhelp </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-b</span><span class="token plain"> ~/dotfiles </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--conflicts</span><span class="token plain"> </span><span class="token comment" style="color:rgb(98, 114, 164)"># find keys bound multiple times</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">confhelp </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-b</span><span class="token plain"> ~/dotfiles </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--check</span><span class="token plain">    </span><span class="token comment" style="color:rgb(98, 114, 164)"># find patterns your regex missed</span><br></span></code></pre></div></div>
<p>Output is pipe-delimited: <code>[type]|key|description|file:line</code></p>
<p>Each entry includes the exact file and line number. The <code>--edit</code> flag gives you jump-to-definition out of the box.</p>
<p>Set <code>base_dirs</code> in your config.toml and you can just run <code>confhelp</code> without the <code>-b</code> flag.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fzf-integration">fzf Integration<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/30/dynamic-shortcuts-help-system#fzf-integration" class="hash-link" aria-label="Direct link to fzf Integration" title="Direct link to fzf Integration" translate="no">​</a></h2>
<p>For advanced workflows (like combining with tealdeer), a wrapper script spawns a centered popup with mode switching:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">selection</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">confhelp </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-b</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$DOTFILES</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable function" style="color:rgb(80, 250, 123);font-style:italic">column</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-t</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> -s</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">'|'</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> fzf </span><span class="token variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">\</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">    </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--header</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">=</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">'Enter=jump | Ctrl+G=tealdeer'</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">\</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">    </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--bind</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">=</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">'ctrl-g:become(echo SWITCH_TLDR)'</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">\</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">    </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--height</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">=</span><span class="token variable number" style="color:rgb(189, 147, 249);font-style:italic">100</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">% </span><span class="token variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">\</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">    </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--layout</span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">reverse</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$selection</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">then</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">file_line</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">echo</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$selection</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable function" style="color:rgb(80, 250, 123);font-style:italic">awk</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">'{print $NF}'</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">file</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">echo</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$file_line</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable function" style="color:rgb(80, 250, 123);font-style:italic">cut</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> -d: </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f1</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">line</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable builtin class-name" style="color:rgb(189, 147, 249);font-style:italic">echo</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable string variable" style="color:rgb(189, 147, 249);font-style:italic">$file_line</span><span class="token variable string" style="color:rgb(255, 121, 198);font-style:italic">"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable function" style="color:rgb(80, 250, 123);font-style:italic">cut</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> -d: </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f2</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    nvim </span><span class="token string" style="color:rgb(255, 121, 198)">"+</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$line</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${DOTFILES}</span><span class="token string" style="color:rgb(255, 121, 198)">/</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${file}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">fi</span><br></span></code></pre></div></div>
<p>The <code>become()</code> action replaces fzf with a new command without closing the terminal. Critical for smooth mode switching between bindings and tealdeer.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tealdeer-custom-pages">Tealdeer Custom Pages<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/30/dynamic-shortcuts-help-system#tealdeer-custom-pages" class="hash-link" aria-label="Direct link to Tealdeer Custom Pages" title="Direct link to Tealdeer Custom Pages" translate="no">​</a></h2>
<p>Tealdeer reads custom pages from a configured directory:</p>
<div class="language-toml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-toml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># ~/.config/tealdeer/config.toml</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token table class-name">directories</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key property">custom_pages_dir</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"/home/decoder/.local/share/tealdeer/pages"</span><br></span></code></pre></div></div>
<p>Custom pages need <code>.page.md</code> extension:</p>
<div class="language-markdown codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-markdown codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token title important punctuation" style="color:rgb(248, 248, 242)">#</span><span class="token title important"> tmux-copy-mode</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token blockquote punctuation" style="color:rgb(248, 248, 242)">&gt;</span><span class="token plain"> Tmux copy mode with vi keybindings</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token list punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> Enter copy mode:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`prefix + [`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token list punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> Start selection:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`v`</span><span class="token plain"> or </span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`V`</span><span class="token plain"> for line</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token list punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> Copy selection:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token code-snippet code keyword" style="color:rgb(189, 147, 249);font-style:italic">`y`</span><br></span></code></pre></div></div>
<p>Ctrl+G in the bindings popup switches to tealdeer browser. Custom pages appear with <code>[custom]</code> marker.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="result">Result<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/30/dynamic-shortcuts-help-system#result" class="hash-link" aria-label="Direct link to Result" title="Direct link to Result" translate="no">​</a></h2>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Aspect</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Before</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">After</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Source of truth</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">shortcuts.yaml (manual)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Config files (parsed)</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Maintenance</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Constant drift</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Zero</td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Jump to definition</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Search manually</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Enter → nvim at line</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Workflow docs</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Scattered notes</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">tealdeer pages</td></tr></tbody></table></div>
<p>Add a binding to <code>.tmux.conf</code>, it appears in the popup immediately. No yaml to update.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gotchas">Gotchas<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/30/dynamic-shortcuts-help-system#gotchas" class="hash-link" aria-label="Direct link to Gotchas" title="Direct link to Gotchas" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Tealdeer file extension</strong> - Custom pages must end in <code>.page.md</code>, not <code>.md</code>.</p>
</li>
<li class="">
<p><strong>fzf become() vs execute()</strong> - <code>execute()</code> runs a command and returns to fzf. <code>become()</code> replaces fzf entirely. For mode switching, you need <code>become()</code>.</p>
</li>
<li class="">
<p><strong>Column alignment</strong> - The parser outputs pipe-separated values. <code>column -t -s'|'</code> aligns them.</p>
</li>
</ol>
<hr>
<p><strong>Resources:</strong></p>
<ul>
<li class=""><a href="https://pypi.org/project/confhelp/" target="_blank" rel="noopener noreferrer" class="">confhelp on PyPI</a></li>
<li class=""><a href="https://github.com/Piotr1215/confhelp" target="_blank" rel="noopener noreferrer" class="">confhelp on GitHub</a></li>
<li class=""><a href="https://github.com/Piotr1215/confhelp/blob/main/examples/alacritty-popup.sh" target="_blank" rel="noopener noreferrer" class="">Example wrapper script</a></li>
<li class=""><a href="https://dbrgn.github.io/tealdeer/" target="_blank" rel="noopener noreferrer" class="">tealdeer docs</a></li>
<li class="">Inspired by <a href="https://github.com/sarthakbhatkar1/Extracto" target="_blank" rel="noopener noreferrer" class="">Extracto</a></li>
</ul>]]></content:encoded>
            <category>dotfiles</category>
            <category>terminal</category>
            <category>fzf</category>
            <category>tealdeer</category>
            <category>productivity</category>
            <category>python</category>
        </item>
        <item>
            <title><![CDATA[Your Kubernetes Cluster is Unbalanced (And the Scheduler Won't Fix It)]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing</guid>
            <pubDate>Mon, 29 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[The Kubernetes scheduler places pods once and never looks back. Descheduler fixes the imbalance by evicting pods from overutilized nodes.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Descheduler" src="https://frosty-babbage-3125a3.netlify.app/assets/images/descheduler-hero-bad49ccdd834f462599523ee82ed907b.jpg" width="4747" height="3165" class="img_ev3q"></p>
<p><em>Photo by <a href="https://unsplash.com/@plhnk" target="_blank" rel="noopener noreferrer" class="">Paul Hanaoka</a> on <a href="https://unsplash.com/photos/a-long-hallway-with-glass-doors-leading-to-another-room-s0XabTAKvak" target="_blank" rel="noopener noreferrer" class="">Unsplash</a></em></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>The Kubernetes scheduler is lazy. It places pods when they're created, picks the best node at that moment, and never thinks about it again. Weeks later, one node is at 75% memory while another sits at 40%. The scheduler doesn't care - its job was done the moment the pod started.</p>
<p><a href="https://github.com/kubernetes-sigs/descheduler" target="_blank" rel="noopener noreferrer" class="">Descheduler</a> fixes this. It runs every few minutes, finds imbalanced nodes, and evicts pods so the scheduler gets another chance to place them better. Set it up once, cluster self-balances automatically.</p>
<p>Here's how to configure it for a homelab. Takes about 20 minutes to get right.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-matters">Why This Matters<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#why-this-matters" class="hash-link" aria-label="Direct link to Why This Matters" title="Direct link to Why This Matters" translate="no">​</a></h2>
<p>Kubernetes scheduling is a point-in-time decision. When a pod is created, the scheduler picks the best available node based on current conditions. But clusters are dynamic - nodes get added, pods come and go, resource requests change with updates.</p>
<p>After a few weeks, you end up with hot nodes and cold nodes. Here's what my homelab looked like:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">NAME           CPU(cores)   MEMORY%</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kube-worker1   438m         75%       &lt;-- carrying the load</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kube-worker4   266m         41%       &lt;-- sitting idle</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kube-worker6   171m         42%       &lt;-- sitting idle</span><br></span></code></pre></div></div>
<p>Worker1 was handling most of the work while worker4 and worker6 did nothing.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="requests-vs-actual-usage">Requests vs Actual Usage<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#requests-vs-actual-usage" class="hash-link" aria-label="Direct link to Requests vs Actual Usage" title="Direct link to Requests vs Actual Usage" translate="no">​</a></h2>
<p>Before configuring descheduler, you need to understand what it actually measures. This is where most people get it wrong.</p>
<p><strong>Descheduler uses REQUESTED resources, not actual usage.</strong></p>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Metric</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Command</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Descheduler Uses?</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Actual CPU/Memory</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">kubectl top nodes</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">No</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Requested CPU/Memory</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">kubectl describe node</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Yes</td></tr></tbody></table></div>
<p>A node might show 5% actual CPU usage but 70% requested. From a scheduling perspective, that node is "full" - the scheduler reserved that capacity even if the pods aren't using it.</p>
<p>Check what descheduler sees:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl describe </span><span class="token function" style="color:rgb(80, 250, 123)">node</span><span class="token plain"> kube-worker1 </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-A5</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Allocated resources"</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">Allocated resources:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  Resource           Requests         Limits</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  --------           --------         ------</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  cpu                2368m (69%)      5490m (161%)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  memory             4769334Ki (65%)  9343689984 (126%)</span><br></span></code></pre></div></div>
<p>Those percentages (69% CPU, 65% memory) are what descheduler uses. Not <code>kubectl top nodes</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="install-descheduler">Install Descheduler<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#install-descheduler" class="hash-link" aria-label="Direct link to Install Descheduler" title="Direct link to Install Descheduler" translate="no">​</a></h2>
<p>Descheduler runs as a CronJob. The Helm chart is the easiest way:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># values.yaml</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> CronJob</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">schedule</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"*/5 * * * *"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">deschedulerPolicy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">profiles</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> default</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">pluginConfig</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> DefaultEvictor</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">ignorePvcPods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">evictLocalStoragePods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">false</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> LowNodeUtilization</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">thresholds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">55</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">pods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">targetThresholds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">70</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">70</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">              </span><span class="token key atrule">pods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">50</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">plugins</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">balance</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">enabled</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> LowNodeUtilization</span><br></span></code></pre></div></div>
<p>Deploy:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">helm </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> descheduler descheduler </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--repo</span><span class="token plain"> https://kubernetes-sigs.github.io/descheduler/ </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--namespace</span><span class="token plain"> descheduler </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  --create-namespace </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f</span><span class="token plain"> values.yaml</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="understanding-thresholds">Understanding Thresholds<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#understanding-thresholds" class="hash-link" aria-label="Direct link to Understanding Thresholds" title="Direct link to Understanding Thresholds" translate="no">​</a></h2>
<p>This is where I wasted an hour. LowNodeUtilization has two threshold sets that work differently:</p>
<p><strong><code>thresholds</code></strong> - defines UNDERUTILIZED nodes</p>
<ul>
<li class="">A node is underutilized when ALL metrics are BELOW these values</li>
<li class="">These nodes receive evicted pods</li>
</ul>
<p><strong><code>targetThresholds</code></strong> - defines OVERUTILIZED nodes</p>
<ul>
<li class="">A node is overutilized when ANY metric is ABOVE these values</li>
<li class="">Pods get evicted FROM these nodes</li>
</ul>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">                    thresholds        targetThresholds</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">                         |                   |</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  UNDERUTILIZED         |    BALANCED       |    OVERUTILIZED</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  (receives pods)       |                   |    (evicts pods)</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  &lt;---------------------|-------------------|--------------------&gt;</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">         0%            55%                 70%               100%</span><br></span></code></pre></div></div>
<p>Descheduler evicts pods from overutilized nodes. The scheduler then places them on underutilized nodes.</p>
<p><strong>Critical</strong>: If no nodes qualify as underutilized, descheduler does nothing. If no nodes qualify as overutilized, descheduler does nothing. Both conditions must be true.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tuning-for-your-cluster">Tuning for Your Cluster<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#tuning-for-your-cluster" class="hash-link" aria-label="Direct link to Tuning for Your Cluster" title="Direct link to Tuning for Your Cluster" translate="no">​</a></h2>
<p>The default thresholds (cpu: 20, memory: 20) assume your nodes have low resource requests. Most real clusters have higher utilization - mine certainly did.</p>
<p>Check your actual request percentages first:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token for-or-select variable" style="color:rgb(189, 147, 249);font-style:italic">node</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">kubectl get nodes </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> name </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable function" style="color:rgb(80, 250, 123);font-style:italic">cut</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> -d/ </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-f2</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">do</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"=== </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$node</span><span class="token string" style="color:rgb(255, 121, 198)"> ==="</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  kubectl describe </span><span class="token function" style="color:rgb(80, 250, 123)">node</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$node</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-A3</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Allocated resources"</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-E</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"cpu|memory"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">done</span><br></span></code></pre></div></div>
<p>Then set thresholds based on what you see:</p>
<ul>
<li class="">If your coldest node is at 45% CPU requests, set <code>thresholds.cpu: 55</code> (above it)</li>
<li class="">If your hottest node is at 75% memory requests, set <code>targetThresholds.memory: 70</code> (below it)</li>
</ul>
<p>I started with the defaults and descheduler did nothing. My coldest node had 45% CPU requests - above the 20% threshold. No node qualified as "underutilized" so there was nowhere to put evicted pods.</p>
<p>After checking my actual cluster state:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">thresholds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">55</span><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)"># nodes below 55% CPU are underutilized</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># nodes below 30% memory are underutilized</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">pods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">targetThresholds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">cpu</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">70</span><span class="token plain">      </span><span class="token comment" style="color:rgb(98, 114, 164)"># nodes above 70% CPU are overutilized</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">memory</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">70</span><span class="token plain">   </span><span class="token comment" style="color:rgb(98, 114, 164)"># nodes above 70% memory are overutilized</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">pods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">50</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="protecting-pvc-pods">Protecting PVC Pods<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#protecting-pvc-pods" class="hash-link" aria-label="Direct link to Protecting PVC Pods" title="Direct link to Protecting PVC Pods" translate="no">​</a></h2>
<p>Not all pods should be evicted. This is important:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> DefaultEvictor</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">args</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">ignorePvcPods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain">           </span><span class="token comment" style="color:rgb(98, 114, 164)"># Don't evict pods with PVCs</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">evictLocalStoragePods</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">false</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Don't evict pods with emptyDir</span><br></span></code></pre></div></div>
<p>Why protect PVC pods? Longhorn and similar storage use ReadWriteOnce volumes. Evicting a pod means:</p>
<ol>
<li class="">Old pod terminates</li>
<li class="">Volume detaches from old node</li>
<li class="">New pod schedules on different node</li>
<li class="">Volume attaches to new node</li>
<li class="">Pod starts</li>
</ol>
<p>If step 2 doesn't complete before step 4, you get Multi-Attach errors. The new pod hangs waiting for the volume while the old node still has it attached.</p>
<p>I had <code>ignorePvcPods: false</code> initially. Got several Multi-Attach errors before I figured this out.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="verify-its-working">Verify It's Working<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#verify-its-working" class="hash-link" aria-label="Direct link to Verify It's Working" title="Direct link to Verify It's Working" translate="no">​</a></h2>
<p>After deploying, watch for LowNodeUtilization events:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl get events </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-A</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-i</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"LowNodeUtilization"</span><br></span></code></pre></div></div>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">prometheus   Normal  LowNodeUtilization  pod/kube-state-metrics-7c8b8bf58c-88qqq</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  pod eviction from kube-worker1 node by sigs.k8s.io/descheduler</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vpa          Normal  LowNodeUtilization  pod/vpa-updater-f59cccc88-fp2t6</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  pod eviction from kube-worker1 node by sigs.k8s.io/descheduler</span><br></span></code></pre></div></div>
<p>Check node balance:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token function" style="color:rgb(80, 250, 123)">top</span><span class="token plain"> nodes</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="results">Results<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#results" class="hash-link" aria-label="Direct link to Results" title="Direct link to Results" translate="no">​</a></h2>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Node</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Before</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">After</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">worker1 (hot)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">75% / 77%</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">69% / 65%</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">worker4 (cold)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">50% / 25%</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">50% / 25%</td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">worker5</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">45% / 30%</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">48% / 35%</td></tr></tbody></table></div>
<p>Worker1's request percentage dropped from 75% to 65%. The cluster is balanced now, and descheduler runs every 5 minutes to keep it that way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gotchas">Gotchas<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/29/kubernetes-descheduler-automatic-rebalancing#gotchas" class="hash-link" aria-label="Direct link to Gotchas" title="Direct link to Gotchas" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Thresholds are percentages of REQUESTS</strong> - Don't use <code>kubectl top nodes</code> to set thresholds. Use <code>kubectl describe node</code> to see request percentages.</p>
</li>
<li class="">
<p><strong>ALL metrics must be below thresholds for underutilized</strong> - If you set cpu: 20 but your coldest node has 45% CPU requests, no node qualifies as underutilized. Descheduler does nothing.</p>
</li>
<li class="">
<p><strong>The comparison is greater-than, not greater-than-or-equal</strong> - A node at exactly 70% CPU with <code>targetThresholds.cpu: 70</code> is NOT overutilized. 70 is not greater than 70.</p>
</li>
<li class="">
<p><strong>Jobs get auto-deleted</strong> - CronJob default history limits are low. If debugging, set <code>successfulJobsHistoryLimit: 3</code> to keep completed jobs around for log inspection.</p>
</li>
<li class="">
<p><strong>DaemonSet pods can't be evicted</strong> - Don't count them when calculating node utilization. A node with 8 DaemonSet pods still has room for workloads.</p>
</li>
</ol>
<hr>
<p><strong>Resources:</strong></p>
<ul>
<li class=""><a href="https://github.com/kubernetes-sigs/descheduler" target="_blank" rel="noopener noreferrer" class="">Descheduler GitHub</a></li>
<li class=""><a href="https://github.com/kubernetes-sigs/descheduler#lownodeutilization" target="_blank" rel="noopener noreferrer" class="">LowNodeUtilization docs</a></li>
<li class=""><a href="https://github.com/Piotr1215/homelab/tree/main/gitops/apps/descheduler" target="_blank" rel="noopener noreferrer" class="">My homelab descheduler config</a></li>
</ul>]]></content:encoded>
            <category>kubernetes</category>
            <category>homelab</category>
            <category>descheduler</category>
            <category>resource-management</category>
            <category>scheduling</category>
        </item>
        <item>
            <title><![CDATA[MinIO is Dead. Garage S3 is Better Anyway (Homelab Migration Guide)]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration</guid>
            <pubDate>Mon, 22 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[MinIO CE entered maintenance mode. No more Docker images, stripped console, frozen development. Migrate to Garage S3 before it's too late.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Garage S3 Migration" src="https://frosty-babbage-3125a3.netlify.app/assets/images/garage-hero-100800f67e55b808101ae127f5cbe74f.jpg" width="6654" height="4436" class="img_ev3q"></p>
<p><em>Photo by <a href="https://unsplash.com/@churchoftodd" target="_blank" rel="noopener noreferrer" class="">todd kent</a> on <a href="https://unsplash.com/photos/black-metal-tool-lot-onnJOfF-okU" target="_blank" rel="noopener noreferrer" class="">Unsplash</a></em></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>MinIO Community Edition is dead. On December 3, 2025, MinIO Inc. <a href="https://github.com/minio/minio" target="_blank" rel="noopener noreferrer" class="">announced maintenance mode</a>: no new features, no PR reviews, no Docker images, no RPM/DEB packages. Critical security fixes only "on a case-by-case basis."</p>
<p>This didn't come out of nowhere. Back in May 2025, they gutted the console - the GUI that made MinIO actually usable. What's left is a glorified file browser. User management, policies, replication config? Moved to the paid AIStor product. The whole thing is open source cosplay now - the repo exists, but it's just a funnel to their commercial offering.</p>
<p>The <a href="https://www.reddit.com/r/selfhosted/comments/1pd97nq/minio_is_in_maintenance_mode_and_is_no_longer/" target="_blank" rel="noopener noreferrer" class="">r/selfhosted</a> and <a href="https://news.ycombinator.com/item?id=46136023" target="_blank" rel="noopener noreferrer" class="">Hacker News</a> threads are worth reading. Thousands of Helm charts and CI/CD pipelines depending on <code>minio/minio</code> images are now broken. Bitnami stopped their MinIO builds too.</p>
<p>Time to migrate. Honestly? For a homelab, Garage is the better choice anyway. 50MB footprint vs 500MB+. Written in Rust. Built-in static web hosting. Actively maintained. MinIO's collapse just forced me to make the switch I should have made earlier.</p>
<p>Here's how to set up Garage on Kubernetes. Takes about 15 minutes.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-garage">Why Garage?<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#why-garage" class="hash-link" aria-label="Direct link to Why Garage?" title="Direct link to Why Garage?" translate="no">​</a></h2>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Feature</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">MinIO</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Garage</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Memory Usage</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">500MB+</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">~50MB</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Binary Size</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">~100MB</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">~20MB</td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Language</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Go</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Rust</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Web Console</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Built-in</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Separate (optional)</td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Static Web Hosting</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Limited</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Built-in</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Multi-node</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Complex</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Simple layout system</td></tr></tbody></table></div>
<p>Garage supports multi-node clusters with built-in replication. For a single-node setup where your storage layer (like Longhorn) already handles redundancy, single-node Garage works perfectly.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploy-garage">Deploy Garage<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#deploy-garage" class="hash-link" aria-label="Direct link to Deploy Garage" title="Direct link to Deploy Garage" translate="no">​</a></h2>
<p>This section covers a basic Kubernetes deployment. You can adapt it to your setup - Docker, bare metal, whatever. The <a href="https://garagehq.deuxfleurs.fr/documentation/quick-start/" target="_blank" rel="noopener noreferrer" class="">official docs</a> cover other deployment methods.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-namespace-and-secrets">Create Namespace and Secrets<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#create-namespace-and-secrets" class="hash-link" aria-label="Direct link to Create Namespace and Secrets" title="Direct link to Create Namespace and Secrets" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl create namespace garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Generate secrets</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">RPC_SECRET</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">openssl rand </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-hex</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable number" style="color:rgb(189, 147, 249);font-style:italic">32</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">ADMIN_TOKEN</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">openssl rand </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-hex</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token variable number" style="color:rgb(189, 147, 249);font-style:italic">32</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Store them (save these somewhere safe!)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"RPC Secret: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$RPC_SECRET</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Admin Token: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$ADMIN_TOKEN</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl create secret generic garage-secrets </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  --from-literal</span><span class="token operator">=</span><span class="token plain">rpc-secret</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$RPC_SECRET</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  --from-literal</span><span class="token operator">=</span><span class="token plain">admin-token</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$ADMIN_TOKEN</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> garage</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="configmap">ConfigMap<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#configmap" class="hash-link" aria-label="Direct link to ConfigMap" title="Direct link to ConfigMap" translate="no">​</a></h3>
<p>Garage uses a TOML config file. Create a ConfigMap:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ConfigMap</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">config</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">garage.toml</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">|</span><span class="token scalar string" style="color:rgb(255, 121, 198)"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token scalar string" style="color:rgb(255, 121, 198)">    metadata_dir = "/data/meta"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token scalar string" style="color:rgb(255, 121, 198)">    data_dir = "/data/blocks"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token scalar string" style="color:rgb(255, 121, 198)">    db_engine = "lmdb"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token scalar string" style="color:rgb(255, 121, 198)">    replication_factor = 1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    rpc_bind_addr = "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">3901"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    rpc_public_addr = "garage.garage.svc.cluster.local</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">3901"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    rpc_secret_file = "/secrets/rpc_secret"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">s3_api</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    s3_region = "garage"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    api_bind_addr = "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">3900"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">s3_web</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    bind_addr = "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">3902"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">admin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    api_bind_addr = "</span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">3903"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    admin_token_file = "/secrets/admin_token"</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="secret-file-permissions">Secret File Permissions<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#secret-file-permissions" class="hash-link" aria-label="Direct link to Secret File Permissions" title="Direct link to Secret File Permissions" translate="no">​</a></h3>
<p>Garage reads secrets from files, not environment variables. When Kubernetes mounts a Secret as a volume, the files default to mode 0644 (world-readable). Garage refuses to start with world-readable secret files - it's a security check built into the binary.</p>
<p>The fix is <code>defaultMode: 0600</code> on the volume mount:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> secrets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">secret</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">secretName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secrets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">defaultMode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">0600</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> rpc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secret</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> rpc_secret</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> admin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">token</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> admin_token</span><br></span></code></pre></div></div>
<p>Skip this and Garage exits with "secret file has insecure permissions".</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="deployment">Deployment<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#deployment" class="hash-link" aria-label="Direct link to Deployment" title="Direct link to Deployment" translate="no">​</a></h3>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> apps/v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Deployment</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">replicas</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">strategy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">type</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Recreate  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Important for RWO PVCs</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">selector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">matchLabels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">template</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">labels</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">containers</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">image</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> dxflrs/garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain">v2.1.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">containerPort</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3900</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># S3 API</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">containerPort</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3901</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># RPC</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">containerPort</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3902</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Web</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">containerPort</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3903</span><span class="token plain">  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Admin</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">volumeMounts</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">mountPath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> /data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> config</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">mountPath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> /etc/garage.toml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">subPath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage.toml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> secrets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">mountPath</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> /secrets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">readOnly</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token boolean important">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">volumes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">persistentVolumeClaim</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">claimName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> config</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">configMap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">config</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> secrets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token key atrule">secret</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">secretName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secrets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">defaultMode</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">0600</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token key atrule">items</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> rpc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secret</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> rpc_secret</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> admin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">token</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            </span><span class="token key atrule">path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> admin_token</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="service">Service<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#service" class="hash-link" aria-label="Direct link to Service" title="Direct link to Service" translate="no">​</a></h3>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> Service</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">selector</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">app</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">ports</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> s3</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3900</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> web</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3902</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> admin</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3903</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="pvc">PVC<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#pvc" class="hash-link" aria-label="Direct link to PVC" title="Direct link to PVC" translate="no">​</a></h3>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> PersistentVolumeClaim</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">data</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">accessModes</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> ReadWriteOnce</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">resources</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">requests</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">storage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 50Gi</span><br></span></code></pre></div></div>
<p>Apply everything, wait for the pod to start.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="initialize-garage">Initialize Garage<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#initialize-garage" class="hash-link" aria-label="Direct link to Initialize Garage" title="Direct link to Initialize Garage" translate="no">​</a></h2>
<p>Garage needs a "layout" before it accepts data. This tells it how much storage to use and which zone the node belongs to.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Get the node ID</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> garage deploy/garage -- /garage status</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># You'll see something like:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># ==== HEALTHY NODES ====</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># ID                Hostname  Address         Tags  Zone  Capacity</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># 563e...          garage    10.42.0.1:3901              NO ROLE</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Assign storage (use your node ID)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> garage deploy/garage -- </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /garage layout assign </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-z</span><span class="token plain"> dc1 </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-c</span><span class="token plain"> 50GB 563e</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Apply the layout</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> garage deploy/garage -- </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /garage layout apply </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"> </span><span class="token number">1</span><br></span></code></pre></div></div>
<p>Now create an access key:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> garage deploy/garage -- </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /garage key create garage-admin</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Output:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Key name: garage-admin</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Key ID: GK0ff60c017ac3f70efb9772f4</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Secret key: 598d28be9a91fc0b0e854454419f091cd6a704b2c121e8a99eab8f9e964e1bf0</span><br></span></code></pre></div></div>
<p>Save these. You'll need them for any S3 client.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="test-it">Test It<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#test-it" class="hash-link" aria-label="Direct link to Test It" title="Direct link to Test It" translate="no">​</a></h2>
<p>Create a bucket and upload something:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> garage deploy/garage -- </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /garage bucket create test-bucket</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">kubectl </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-n</span><span class="token plain"> garage deploy/garage -- </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  /garage bucket allow </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--read</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--write</span><span class="token plain"> test-bucket </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--key</span><span class="token plain"> garage-admin</span><br></span></code></pre></div></div>
<p>From outside the cluster, use AWS CLI:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">aws configure </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--profile</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Access Key: GK0ff60c017ac3f70efb9772f4</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Secret Key: (your secret)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Region: garage</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Output format: json</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">aws </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--profile</span><span class="token plain"> garage --endpoint-url http://garage.garage.svc:3900 </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  s3 </span><span class="token function" style="color:rgb(80, 250, 123)">ls</span><br></span></code></pre></div></div>
<p>That's it. You have a working S3-compatible storage system.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="my-homelab-integration">My Homelab Integration<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#my-homelab-integration" class="hash-link" aria-label="Direct link to My Homelab Integration" title="Direct link to My Homelab Integration" translate="no">​</a></h2>
<p>The above is all you need for basic Garage. The rest of this post covers how I integrated it into my specific homelab setup: GitOps secrets management, ingress routing, automation, and document sync.</p>
<p>My homelab runs on a 7-node Kubernetes cluster (1 control plane, 6 workers) across 3 Proxmox hosts. Storage is Longhorn, ingress is Envoy Gateway, everything deploys via ArgoCD from a <a href="https://github.com/Piotr1215/homelab" target="_blank" rel="noopener noreferrer" class="">GitOps repo</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="secrets-with-external-secrets-operator">Secrets with External Secrets Operator<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#secrets-with-external-secrets-operator" class="hash-link" aria-label="Direct link to Secrets with External Secrets Operator" title="Direct link to Secrets with External Secrets Operator" translate="no">​</a></h3>
<p>The basic setup above uses <code>kubectl create secret</code>. That works, but for GitOps you need secrets in your repo - and committing plaintext secrets is a security risk.</p>
<p>I use External Secrets Operator (ESO) with Bitwarden Secrets Manager:</p>
<ol>
<li class="">Generate secrets locally with <code>openssl rand -hex 32</code></li>
<li class="">Store them in Bitwarden Secrets Manager (gives you a UUID)</li>
<li class="">Create an ExternalSecret that references the UUID</li>
<li class="">ESO syncs the secret from Bitwarden into Kubernetes</li>
</ol>
<p>The ExternalSecret references UUIDs, not values - safe to commit:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> external</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secrets.io/v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ExternalSecret</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secrets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">refreshInterval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 1h</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">secretStoreRef</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> bitwarden</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secretsmanager</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> ClusterSecretStore</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">target</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secrets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">data</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">secretKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> rpc</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">secret</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">remoteRef</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 7b5d53a8</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">xxxx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">xxxx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">xxxx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">xxxxxxxxxxxx</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">secretKey</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> admin</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">token</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">remoteRef</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">key</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> 8c6e64b9</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">xxxx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">xxxx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">xxxx</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">xxxxxxxxxxxx</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="httproutes-with-envoy-gateway">HTTPRoutes with Envoy Gateway<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#httproutes-with-envoy-gateway" class="hash-link" aria-label="Direct link to HTTPRoutes with Envoy Gateway" title="Direct link to HTTPRoutes with Envoy Gateway" translate="no">​</a></h3>
<p>Instead of NodePort or LoadBalancer, I use Envoy Gateway with HTTPRoutes. For simple services, my <a href="https://github.com/Piotr1215/httproute-controller" target="_blank" rel="noopener noreferrer" class="">httproute-controller</a> generates HTTPRoutes from Service annotations automatically. Here's the Garage config:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token key atrule">apiVersion</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> gateway.networking.k8s.io/v1</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">kind</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> HTTPRoute</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">metadata</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">s3</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> envoy</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">gateway</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">system</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key atrule">spec</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">parentRefs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> homelab</span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain">gateway</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token key atrule">sectionName</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> https</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">hostnames</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"s3.garage.homelab.local"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"*.s3.garage.homelab.local"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token key atrule">rules</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">backendRefs</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">-</span><span class="token plain"> </span><span class="token key atrule">name</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">namespace</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> garage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token key atrule">port</span><span class="token punctuation" style="color:rgb(248, 248, 242)">:</span><span class="token plain"> </span><span class="token number">3900</span><br></span></code></pre></div></div>
<p>The wildcard <code>*.s3.garage.homelab.local</code> enables virtual-hosted bucket access (<code>mybucket.s3.garage.homelab.local</code>).</p>
<p>For cross-namespace routing, you need a ReferenceGrant in the garage namespace allowing the HTTPRoute to reference the Service.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="webui">WebUI<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#webui" class="hash-link" aria-label="Direct link to WebUI" title="Direct link to WebUI" translate="no">​</a></h3>
<p>Garage has no built-in UI. <a href="https://github.com/khairul169/garage-webui" target="_blank" rel="noopener noreferrer" class="">garage-webui</a> fills that gap - runs as a sidecar, connects to Garage's admin API on port 3903.</p>
<p>See my <a href="https://github.com/Piotr1215/homelab/blob/main/gitops/apps/garage/garage.yaml" target="_blank" rel="noopener noreferrer" class="">deployment manifest</a> for the full setup with WebUI.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="justfile-recipes">Justfile Recipes<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#justfile-recipes" class="hash-link" aria-label="Direct link to Justfile Recipes" title="Direct link to Justfile Recipes" translate="no">​</a></h3>
<p>I use a <a href="https://github.com/Piotr1215/homelab/blob/main/justfile" target="_blank" rel="noopener noreferrer" class="">justfile</a> for cluster operations. Two recipes for Garage:</p>
<ul>
<li class=""><code>garage-bucket</code> - creates buckets with read/write permissions</li>
<li class=""><code>garage-upload</code> - uploads files/folders, extracts credentials from Garage automatically</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="rclone-bisync">rclone Bisync<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#rclone-bisync" class="hash-link" aria-label="Direct link to rclone Bisync" title="Direct link to rclone Bisync" translate="no">​</a></h3>
<p>For syncing local folders to Garage, I use rclone with bisync. My <code>~/.config/rclone/rclone.conf</code>:</p>
<div class="language-ini codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-ini codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token section punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token section section-name selector" style="color:rgb(255, 121, 198)">garage</span><span class="token section punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">type</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">s3</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">provider</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">Other</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">access_key_id</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">GK0ff60c017ac3f70efb9772f4</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">secret_access_key</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">(your secret)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">endpoint</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">https://s3.garage.homelab.local</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">region</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">garage</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token key attr-name" style="color:rgb(241, 250, 140)">no_check_bucket</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">=</span><span class="token plain"> </span><span class="token value attr-value">true</span><br></span></code></pre></div></div>
<p>Two gotchas:</p>
<ul>
<li class=""><strong><code>region = garage</code></strong> is required. Without it, rclone defaults to <code>us-east-1</code> and Garage rejects with "AuthorizationHeaderMalformed"</li>
<li class=""><strong>Self-signed certs</strong> need <code>--no-check-certificate</code> on every command</li>
</ul>
<p>I run bisync daily via cron with local folder as source of truth.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="static-web-hosting">Static Web Hosting<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#static-web-hosting" class="hash-link" aria-label="Direct link to Static Web Hosting" title="Direct link to Static Web Hosting" translate="no">​</a></h3>
<p>Garage has built-in static web hosting. Enable it per bucket in the WebUI (or via CLI), access at <code>https://&lt;bucket&gt;.web.garage.homelab.local</code>. No nginx, no separate web server.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gotchas">Gotchas<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#gotchas" class="hash-link" aria-label="Direct link to Gotchas" title="Direct link to Gotchas" translate="no">​</a></h2>
<ol>
<li class="">
<p><strong>Layout must be applied</strong> - Garage won't accept data until you assign capacity and apply the layout. The pod starts fine, but S3 operations fail.</p>
</li>
<li class="">
<p><strong>PVC access mode</strong> - Use <code>ReadWriteOnce</code> with <code>Recreate</code> strategy. Rolling updates hang if the old pod holds the PVC.</p>
</li>
<li class="">
<p><strong>WebUI needs Garage v2</strong> - The webui uses v2 admin API. Don't use Garage v1.x.</p>
</li>
<li class="">
<p><strong>HTTPRoute namespace</strong> - If using Gateway API, routes in <code>default</code> namespace won't match ReferenceGrants for your gateway namespace.</p>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="results">Results<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/22/minio-to-garage-migration#results" class="hash-link" aria-label="Direct link to Results" title="Direct link to Results" translate="no">​</a></h2>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Item</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Before</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">After</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">RAM Usage</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">~500MB</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">~50MB</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Pods</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">2 (MinIO + console)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">2 (Garage + WebUI)</td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Static hosting</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">nginx sidecar</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Built-in</td></tr></tbody></table></div>
<p>450MB less RAM. Built-in static web hosting.</p>
<hr>
<p><strong>Resources:</strong></p>
<ul>
<li class=""><a href="https://garagehq.deuxfleurs.fr/" target="_blank" rel="noopener noreferrer" class="">Garage Documentation</a></li>
<li class=""><a href="https://github.com/khairul169/garage-webui" target="_blank" rel="noopener noreferrer" class="">garage-webui</a></li>
<li class=""><a href="https://github.com/Piotr1215/homelab" target="_blank" rel="noopener noreferrer" class="">My homelab repo</a>
<ul>
<li class=""><a href="https://github.com/Piotr1215/homelab/blob/main/gitops/apps/garage/garage.yaml" target="_blank" rel="noopener noreferrer" class="">Garage deployment</a></li>
<li class=""><a href="https://github.com/Piotr1215/homelab/tree/main/gitops/apps/envoy-gateway-resources" target="_blank" rel="noopener noreferrer" class="">HTTPRoutes</a></li>
</ul>
</li>
<li class=""><a href="https://rclone.org/s3/" target="_blank" rel="noopener noreferrer" class="">rclone S3 docs</a></li>
</ul>]]></content:encoded>
            <category>homelab</category>
            <category>kubernetes</category>
            <category>s3</category>
            <category>storage</category>
            <category>garage</category>
            <category>minio</category>
            <category>gitops</category>
        </item>
        <item>
            <title><![CDATA[Mozilla Wants AI in Your Browser? Move to LibreWolf Instead]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration</guid>
            <pubDate>Wed, 17 Dec 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Quick Firefox to LibreWolf migration guide for Linux]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="LibreWolf Migration" src="https://frosty-babbage-3125a3.netlify.app/assets/images/librewolf-hero-a9d4bd465f1f84ec5bda74094c003215.png" width="400" height="400" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p>Mozilla just <a href="https://blog.mozilla.org/en/mozilla/leadership/mozillas-next-chapter-anthony-enzor-demeo-new-ceo/" target="_blank" rel="noopener noreferrer" class="">named a new CEO</a>. The headline promise? Firefox will "evolve into a modern AI browser." I read that and immediately started looking for alternatives.</p>
<p>This is a quick migration guide, not comprehensive documentation. Check <a href="https://librewolf.net/docs/faq/" target="_blank" rel="noopener noreferrer" class="">LibreWolf's official docs</a> for your specific system.</p>
<p>I was worried about breaking things—my setup has shell aliases for profile switching, wrapper scripts, keyboard shortcuts that launch Firefox, and window management automation. Everything migrated in about 30 minutes. If it worked for me, it'll work for you.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-leave-firefox">Why Leave Firefox?<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#why-leave-firefox" class="hash-link" aria-label="Direct link to Why Leave Firefox?" title="Direct link to Why Leave Firefox?" translate="no">​</a></h2>
<p>Mozilla's December 2025 announcement outlined three priorities:</p>
<ol>
<li class="">AI must be "clear and understandable"</li>
<li class="">Revenue diversification beyond search</li>
<li class="">Firefox growing into "a broader ecosystem"</li>
</ol>
<p>None of these say "leave users alone."</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-alternatives">The Alternatives<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#the-alternatives" class="hash-link" aria-label="Direct link to The Alternatives" title="Direct link to The Alternatives" translate="no">​</a></h2>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Browser</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Engine</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Key Features</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">LibreWolf</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Gecko (Firefox fork)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Zero telemetry, uBlock Origin pre-installed, hardened defaults</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Mullvad Browser</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Gecko (Tor fork)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Best anti-fingerprinting, designed for VPN use</td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Ungoogled Chromium</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Chromium</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Pure Chromium with all Google code removed, truly FOSS</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Zen Browser</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Gecko (Firefox fork)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Privacy-focused, vertical tabs, minimal UI</td></tr></tbody></table></div>
<p><strong>My pick: <a href="https://librewolf.net/" target="_blank" rel="noopener noreferrer" class="">LibreWolf</a>.</strong> It's Firefox without Mozilla's telemetry. Same Gecko engine, same extensions, same workflow. uBlock Origin comes pre-installed. Updates track Firefox releases within days (currently at Firefox 146). Firefox Sync is disabled by default but you can enable it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="installation">Installation<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#installation" class="hash-link" aria-label="Direct link to Installation" title="Direct link to Installation" translate="no">​</a></h2>
<p>Multiple options. This guide uses Flatpak:</p>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Method</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Install Command</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Notes</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Flatpak</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">flatpak install flathub io.gitlab.librewolf-community</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Sandboxed, auto-updates, profiles in ~/.var/app/...</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Native (Debian/Ubuntu)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">sudo apt install extrepo &amp;&amp; sudo extrepo enable librewolf &amp;&amp; sudo apt install librewolf</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Faster startup, profiles in ~/.librewolf/</td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Native (Arch)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">yay -S librewolf</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">AUR package, native performance</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">AppImage</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Download from librewolf.net</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Portable, no install needed</td></tr></tbody></table></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">flatpak </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> flathub io.gitlab.librewolf-community</span><br></span></code></pre></div></div>
<p>I used Flatpak because it's available on Pop!_OS. Trade-off: slower startup, profiles live in <code>~/.var/app/io.gitlab.librewolf-community/.librewolf/</code>.</p>
<p>For webcam access (video calls), grant device permissions:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">flatpak override </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--user</span><span class="token plain"> io.gitlab.librewolf-community </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--device</span><span class="token operator">=</span><span class="token plain">all</span><br></span></code></pre></div></div>
<p>If you choose native install, adjust paths from <code>~/.var/app/io.gitlab.librewolf-community/.librewolf/</code> to <code>~/.librewolf/</code>, and binary from <code>flatpak run io.gitlab.librewolf-community</code> to <code>librewolf</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="migrating-data">Migrating Data<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#migrating-data" class="hash-link" aria-label="Direct link to Migrating Data" title="Direct link to Migrating Data" translate="no">​</a></h2>
<p>LibreWolf can import everything from Firefox in one step:</p>
<p>Settings → General → Import Browser Data → <strong>Import Data</strong></p>
<p>This imports bookmarks, passwords, history, and autofill data.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fix-broken-sites">Fix Broken Sites<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#fix-broken-sites" class="hash-link" aria-label="Direct link to Fix Broken Sites" title="Direct link to Fix Broken Sites" translate="no">​</a></h2>
<p>LibreWolf's strict tracking protection can break some sites. Enable this:</p>
<p>Settings → Privacy &amp; Security → Enhanced Tracking Protection → <strong>Fix major site issues (recommended)</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="keep-logins-persistent">Keep Logins Persistent<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#keep-logins-persistent" class="hash-link" aria-label="Direct link to Keep Logins Persistent" title="Direct link to Keep Logins Persistent" translate="no">​</a></h2>
<p>By default, LibreWolf deletes cookies when closed—you'll have to re-login everywhere. Either disable it or add exceptions for sites you use:</p>
<p>Settings → Privacy &amp; Security → <strong>Delete cookies and site data when LibreWolf is closed</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="setting-up-profiles">Setting Up Profiles<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#setting-up-profiles" class="hash-link" aria-label="Direct link to Setting Up Profiles" title="Direct link to Setting Up Profiles" translate="no">​</a></h2>
<p>LibreWolf supports Firefox-style profiles:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Open profile manager</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">flatpak run io.gitlab.librewolf-community </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-P</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Or launch specific profile</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">flatpak run io.gitlab.librewolf-community </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-P</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Work"</span><br></span></code></pre></div></div>
<p>If you use multiple Firefox profiles, create matching ones in LibreWolf's profile manager, then copy the data:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Copy bookmarks and passwords (adjust profile names)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> ~/.mozilla/firefox/PROFILE_NAME/places.sqlite </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   ~/.var/app/io.gitlab.librewolf-community/.librewolf/PROFILE_NAME/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> ~/.mozilla/firefox/PROFILE_NAME/logins.json </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   ~/.var/app/io.gitlab.librewolf-community/.librewolf/PROFILE_NAME/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cp</span><span class="token plain"> ~/.mozilla/firefox/PROFILE_NAME/key4.db </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   ~/.var/app/io.gitlab.librewolf-community/.librewolf/PROFILE_NAME/</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="extensions">Extensions<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#extensions" class="hash-link" aria-label="Direct link to Extensions" title="Direct link to Extensions" translate="no">​</a></h2>
<p>Same Firefox Add-ons store. uBlock Origin comes pre-installed.</p>
<p>Extension settings don't transfer automatically, but most extensions support exporting settings to JSON. Vimium-C, uBlock Origin, and others have Settings → Export/Import. Quick way to move your keybindings and configurations.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="updating-dotfiles">Updating Dotfiles<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#updating-dotfiles" class="hash-link" aria-label="Direct link to Updating Dotfiles" title="Direct link to Updating Dotfiles" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="shell-aliases">Shell Aliases<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#shell-aliases" class="hash-link" aria-label="Direct link to Shell Aliases" title="Direct link to Shell Aliases" translate="no">​</a></h3>
<p>Update any aliases that reference <code>firefox</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">alias</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">firefox</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">'flatpak run io.gitlab.librewolf-community'</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="system-wide-wrapper">System-Wide Wrapper<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#system-wide-wrapper" class="hash-link" aria-label="Direct link to System-Wide Wrapper" title="Direct link to System-Wide Wrapper" translate="no">​</a></h3>
<p>Aliases don't work in scripts or desktop files. Create a wrapper so all <code>firefox</code> calls use LibreWolf:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">tee</span><span class="token plain"> /usr/local/bin/firefox </span><span class="token operator">&lt;&lt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'EOF'</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">#!/bin/bash</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">exec flatpak run io.gitlab.librewolf-community "$@"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EOF</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> +x /usr/local/bin/firefox</span><br></span></code></pre></div></div>
<p>This is the key trick—no changes needed to existing scripts. Everything that calls <code>firefox</code> just works.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="default-browser">Default Browser<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#default-browser" class="hash-link" aria-label="Direct link to Default Browser" title="Direct link to Default Browser" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">xdg-settings </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">set</span><span class="token plain"> default-web-browser io.gitlab.librewolf-community.desktop</span><br></span></code></pre></div></div>
<p>Verify:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">xdg-settings get default-web-browser</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># io.gitlab.librewolf-community.desktop</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-changes">What Changes<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#what-changes" class="hash-link" aria-label="Direct link to What Changes" title="Direct link to What Changes" translate="no">​</a></h2>
<table><thead><tr><th>Item</th><th>Firefox</th><th>LibreWolf (Flatpak)</th></tr></thead><tbody><tr><td>Binary</td><td><code>firefox</code></td><td><code>flatpak run io.gitlab.librewolf-community</code></td></tr><tr><td>Profile path</td><td><code>~/.mozilla/firefox/</code></td><td><code>~/.var/app/io.gitlab.librewolf-community/.librewolf/</code></td></tr><tr><td>Telemetry</td><td>On by default</td><td>Disabled, can't enable</td></tr><tr><td>Firefox Sync</td><td>Enabled</td><td>Disabled by default (can enable)</td></tr><tr><td>uBlock Origin</td><td>Manual install</td><td>Pre-installed</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-result">The Result<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/12/17/firefox-to-librewolf-migration#the-result" class="hash-link" aria-label="Direct link to The Result" title="Direct link to The Result" translate="no">​</a></h2>
<p>Everything works. Firefox stays installed as backup for now.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">sudo</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">apt</span><span class="token plain"> remove firefox</span><br></span></code></pre></div></div>]]></content:encoded>
            <category>linux</category>
            <category>privacy</category>
            <category>firefox</category>
            <category>librewolf</category>
            <category>browser</category>
            <category>dotfiles</category>
            <category>flatpak</category>
        </item>
        <item>
            <title><![CDATA[From Physical Servers to vCluster: Understanding Kubernetes Multi-Tenancy]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder</guid>
            <pubDate>Thu, 23 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn why vCluster exists by understanding the evolution from physical servers through VMs, containers, and Kubernetes. A first-principles explanation of Kubernetes multi-tenancy.]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="The computing abstraction ladder showing progression from physical hardware to virtual Kubernetes clusters" src="https://frosty-babbage-3125a3.netlify.app/assets/images/computing-abstraction-hero-fc55fbc7740d38717cc2626d149481f5.png" width="1920" height="1080" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
article mark {
  background-color: rgba(255, 102, 0, 0.2);
  color: #000;
  font-weight: 600;
  padding: 2px 6px;
  border-radius: 3px;
}

@media (prefers-color-scheme: dark) {
  article mark {
    background-color: rgba(255, 102, 0, 0.3);
    color: #fff;
  }
}
.tldr-box {
  background: rgba(255, 102, 0, 0.08);
  border-left: 4px solid #FF6600;
  padding: 20px;
  margin: 20px 0;
  border-radius: 4px;
}
.tldr-box strong {
  color: #FF6600;
}
</style>
<div class="tldr-box"><p><strong>TL;DR:</strong> This blog explains the evolution of computing abstractions from physical servers to virtual Kubernetes clusters.</p><p>Each layer solved a real problem while creating new challenges: physical computers led to VMs (stranded resources), VMs to containers (OS overhead), containers to Kubernetes (orchestration complexity), and Kubernetes to virtual clusters (multi-tenancy isolation).</p><p>vCluster enables teams to run fully functional virtual Kubernetes clusters inside existing infrastructure—providing control plane isolation that namespaces cannot match.</p></div>
<p><strong>Last week I returned from <a href="https://community.cncf.io/events/details/cncf-kcd-uk-presents-kubernetes-community-days-uk-edinburgh-2025/" target="_blank" rel="noopener noreferrer" class="">KCD UK</a> where I led a workshop introducing people to vCluster</strong> (<a href="https://killercoda.com/decoder/course/vcluster/vcluster_introduction" target="_blank" rel="noopener noreferrer" class="">try it yourself here</a>). At the workshop and at our booth, we fielded dozens of questions from people with wildly different backgrounds.</p>
<p>Some attendees had deep Kubernetes expertise but had never heard of virtual clusters. Others worked with containers daily and were exploring the orchestration layer. People came from different knowledge bases and experience levels.</p>
<blockquote>
<p><strong>The most common question? "What is vCluster, and why would I need it?"</strong></p>
</blockquote>
<p>The short answer: vCluster is an open-source solution that enables teams to run virtual Kubernetes clusters inside existing infrastructure. These virtual clusters are Certified Kubernetes Distributions that provide strong workload isolation while running as nested environments on top of another Kubernetes cluster.</p>
<p>But to understand <em>why</em> that matters—and whether you need it—you need to see how we got here.</p>
<p><strong>By understanding this evolution, you'll see why virtual Kubernetes clusters solve real multi-tenancy problems that traditional approaches can't.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-computing-revolution">The Computing Revolution<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#the-computing-revolution" class="hash-link" aria-label="Direct link to The Computing Revolution" title="Direct link to The Computing Revolution" translate="no">​</a></h2>
<p>In the beginning, we had computers. The <a href="https://en.wikipedia.org/wiki/ENIAC" target="_blank" rel="noopener noreferrer" class="">ENIAC</a> filled entire rooms in 1945. The <a href="https://en.wikipedia.org/wiki/Apple_II" target="_blank" rel="noopener noreferrer" class="">Apple II</a> in 1977 and <a href="https://en.wikipedia.org/wiki/IBM_Personal_Computer" target="_blank" rel="noopener noreferrer" class="">IBM PC</a> in 1981 brought computation to offices and homes. You write code, it runs on that hardware. Simple.</p>
<p><img decoding="async" loading="lazy" alt="What It Is" src="https://frosty-babbage-3125a3.netlify.app/assets/images/01-what-it-is-15faa9909c98e3614267a6b5968f7390.png" width="1836" height="1656" class="img_ev3q"></p>
<p>This worked pretty well until you needed to deploy fleets of them. Then the problems became obvious:</p>
<p><strong>The Problems:</strong></p>
<ul>
<li class=""><strong>Single point of failure:</strong> If the machine fails, your computation stops. No redundancy. No failover. Just downtime.</li>
<li class=""><strong>Slow scaling:</strong> Buying another physical machine means racking it, cabling it, installing the OS, and configuring it. Weeks of lead time for capacity you might only need during peak hours.</li>
<li class=""><strong>Resource waste:</strong> Expensive, inflexible hardware sitting idle.</li>
</ul>
<mark>Something had to change.</mark>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="better-together-distributed-computing">Better Together: Distributed Computing<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#better-together-distributed-computing" class="hash-link" aria-label="Direct link to Better Together: Distributed Computing" title="Direct link to Better Together: Distributed Computing" translate="no">​</a></h2>
<p>The obvious first step was networking. <a href="https://en.wikipedia.org/wiki/ARPANET" target="_blank" rel="noopener noreferrer" class="">ARPANET</a> connected four university computers in 1969. The <a href="https://en.wikipedia.org/wiki/Client%E2%80%93server_model" target="_blank" rel="noopener noreferrer" class="">client-server model</a> emerged in the 1980s. By the 1990s, <a href="https://en.wikipedia.org/wiki/Beowulf_cluster" target="_blank" rel="noopener noreferrer" class="">Beowulf clusters</a> let researchers build supercomputers from commodity hardware.</p>
<p>Now you could distribute work. One machine runs your database. Another runs your web server. A third handles batch processing. If one fails, the others keep running. You have redundancy and you can scale by adding more machines.</p>
<p><img decoding="async" loading="lazy" alt="What It Is" src="https://frosty-babbage-3125a3.netlify.app/assets/images/02-what-it-is-387500fd4261acee52f870d23c9ce0cf.png" width="3136" height="1716" class="img_ev3q"></p>
<p>This was better. But it created new problems:</p>
<p><strong>The Problems:</strong></p>
<ul>
<li class=""><strong>Stranded resources:</strong> Your database server runs at 80% CPU while your web server idles at 15%. You can't dynamically shift capacity between machines.</li>
<li class=""><strong>Operational complexity:</strong> Tracking which workload runs where, handling networking and security manually, constantly racking hardware and swapping failed drives.</li>
<li class=""><strong>Cost inefficiency:</strong> Wasting money on idle hardware that can't be repurposed without physically moving cables and reinstalling operating systems.</li>
</ul>
<mark>We needed a way to share physical hardware between workloads.</mark>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="virtual-machines-let-us-share-physical-hardware">Virtual Machines Let Us Share Physical Hardware<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#virtual-machines-let-us-share-physical-hardware" class="hash-link" aria-label="Direct link to Virtual Machines Let Us Share Physical Hardware" title="Direct link to Virtual Machines Let Us Share Physical Hardware" translate="no">​</a></h2>
<p>Then someone had a brilliant idea: what if we could run multiple "virtual" computers on one physical machine?</p>
<p><a href="https://en.wikipedia.org/wiki/VMware" target="_blank" rel="noopener noreferrer" class="">VMware</a> brought this to x86 in 1998. <a href="https://en.wikipedia.org/wiki/Xen" target="_blank" rel="noopener noreferrer" class="">Xen</a> followed in 2003. <a href="https://en.wikipedia.org/wiki/Kernel-based_Virtual_Machine" target="_blank" rel="noopener noreferrer" class="">KVM</a> merged into Linux in 2007. A hypervisor sits between hardware and operating systems, creating virtual machines that behave like physical hardware.</p>
<p>This was significantly better:</p>
<p><strong>The Benefits:</strong></p>
<ul>
<li class=""><strong>Efficient resource sharing:</strong> Run multiple VMs on one physical server. Put that 80% CPU database and 15% CPU web server on the same host.</li>
<li class=""><strong>Dynamic provisioning:</strong> Create VM templates, spin them up and down based on demand, migrate between hosts without downtime.</li>
<li class=""><strong>Dramatic improvement:</strong> Resource utilization improved, provisioning went from weeks to minutes.</li>
</ul>
<p><img decoding="async" loading="lazy" alt="What It Is" src="https://frosty-babbage-3125a3.netlify.app/assets/images/03-what-it-is-5d4686ab69a9e4d60a570cd1c3608f1b.png" width="3136" height="1556" class="img_ev3q"></p>
<p><strong>But then we realized VMs were bloated and heavyweight.</strong></p>
<p><strong>The Problems:</strong></p>
<ul>
<li class=""><strong>Massive overhead:</strong> Each VM runs its own kernel, system libraries, daemons, and network stack—gigabytes of redundant code.</li>
<li class=""><strong>Slow operations:</strong> Booting takes minutes. Downloading and distributing VM images is slow. Migration means moving entire OS images.</li>
<li class=""><strong>Management burden:</strong> Every VM needs patching, monitoring, and management.</li>
</ul>
<mark>You traded hardware sprawl for VM sprawl. We needed something lighter.</mark>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="virtual-machines-can-be-too-slow">Virtual Machines Can Be Too Slow<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#virtual-machines-can-be-too-slow" class="hash-link" aria-label="Direct link to Virtual Machines Can Be Too Slow" title="Direct link to Virtual Machines Can Be Too Slow" translate="no">​</a></h2>
<p>Then someone smart figured out that you could build an abstraction that looks like a regular OS from the perspective of the software running inside, but when that software makes a system call, it goes to the host machine instead.</p>
<p><a href="https://en.wikipedia.org/wiki/LXC" target="_blank" rel="noopener noreferrer" class="">LXC</a> (Linux Containers) emerged in 2008 using <a href="https://en.wikipedia.org/wiki/Linux_namespaces" target="_blank" rel="noopener noreferrer" class="">Linux namespaces and cgroups</a>. But <a href="https://en.wikipedia.org/wiki/Docker_(software)" target="_blank" rel="noopener noreferrer" class="">Docker</a> in 2013 made containers actually usable for everyone.</p>
<p>The key insight: all that extra OS overhead gets shared. A container image packages your application and its dependencies—nothing more. No full OS. No kernel. Just your code and the libraries it needs.</p>
<p><img decoding="async" loading="lazy" alt="What It Is" src="https://frosty-babbage-3125a3.netlify.app/assets/images/04-what-it-is-8a55f9ef35c032333ee435a17b467d5b.png" width="3136" height="1420" class="img_ev3q"></p>
<p><strong>The Benefits:</strong></p>
<ul>
<li class=""><strong>Lightning fast:</strong> Containers start in milliseconds instead of minutes.</li>
<li class=""><strong>Resource efficient:</strong> Megabytes instead of gigabytes.</li>
<li class=""><strong>High density:</strong> Run hundreds of containers on a single host.</li>
<li class=""><strong>Developer productivity:</strong> Replicate production environments locally, deploy hundreds of times per day.</li>
</ul>
<mark>This was a massive improvement.</mark>
<mark>How do you orchestrate hundreds of containers across dozens of machines?</mark>
<p>Docker became very popular and soon people started building all sorts of different containers. A typical deployed system has at least three components: the application, a database, and maybe a proxy like nginx or a cache like redis.</p>
<p>These components need to work together:</p>
<ul>
<li class="">Database comes online first</li>
<li class="">Then redis, then the app, then the proxy</li>
<li class="">Each needs to check the health of the previous one</li>
<li class="">Your app can't start until the database is ready</li>
<li class="">The proxy can't route traffic until the app is healthy</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="containers-rarely-work-alone">Containers Rarely Work Alone<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#containers-rarely-work-alone" class="hash-link" aria-label="Direct link to Containers Rarely Work Alone" title="Direct link to Containers Rarely Work Alone" translate="no">​</a></h3>
<p>You need to <strong>orchestrate</strong> your containers. <a href="https://docs.docker.com/compose/" target="_blank" rel="noopener noreferrer" class="">Docker Compose</a> did this simply for a single host.</p>
<p><strong>But what about across dozens or hundreds of hosts?</strong></p>
<ul>
<li class="">Which host runs which container?</li>
<li class="">How do containers find each other across machines?</li>
<li class="">What happens when a host fails?</li>
<li class="">How do you route traffic?</li>
<li class="">How do you handle dynamic autoscaling based on load?</li>
<li class="">How do you distribute containers across hosts for redundancy?</li>
<li class="">How do you handle rolling updates without downtime?</li>
</ul>
<mark>Running containers at scale without proper orchestration is operationally infeasible.</mark>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="containers-at-scale-need-orchestration">Containers at Scale Need Orchestration<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#containers-at-scale-need-orchestration" class="hash-link" aria-label="Direct link to Containers at Scale Need Orchestration" title="Direct link to Containers at Scale Need Orchestration" translate="no">​</a></h2>
<p>Google had been running <a href="https://en.wikipedia.org/wiki/Borg_(cluster_manager)" target="_blank" rel="noopener noreferrer" class="">Borg</a> internally since 2003, managing billions of containers. They knew the answers. In 2014 they open-sourced <a href="https://en.wikipedia.org/wiki/Kubernetes" target="_blank" rel="noopener noreferrer" class="">Kubernetes</a> and brought that experience to everyone.</p>
<p>The key insight: doing this <strong>declaratively</strong> is very powerful because it's both standardized and reproducible. You don't say "start this container on node-7." You say "I want 3 replicas of this app, each needs 512MB memory" and Kubernetes figures out where to run them.</p>
<p><img decoding="async" loading="lazy" alt="What It Is" src="https://frosty-babbage-3125a3.netlify.app/assets/images/05-what-it-is-d43559a47decc6374cdc150c63e575a6.png" width="3136" height="1260" class="img_ev3q"></p>
<p>Kubernetes treats a cluster of machines as a single compute resource. Scheduling, service discovery, load balancing, self-healing, autoscaling—it handles all of it. The control plane continuously converges reality toward your declared desired state.</p>
<p>This let us build entire compute platforms around this concept. Deploying stuff is really complicated—tons of little knobs and dials needing to be turned and tweaked. In the old days everyone had bespoke frameworks and it was super inefficient.</p>
<p>If we capture those abstractions in a standardized API and make it flexible enough to satisfy lots of use cases, one engineer can work on and scale up many different deployments. The system itself can self-heal if there's a problem.</p>
<mark>This is the major reason people want to use Kubernetes. Multi-tenancy.</mark>
<p><img decoding="async" loading="lazy" alt="Multi-Tenancy Challenges" src="https://frosty-babbage-3125a3.netlify.app/assets/images/07-multi-tenancy-challenges-46a5dfff40a6062b11d3c99c2c8e46ab.png" width="3136" height="1716" class="img_ev3q"></p>
<p>Platform teams need to serve many internal customers—Team A, Team B, Team C, maybe dozens of teams. You want to give them self-service Kubernetes access. But Kubernetes namespaces aren't strong enough for true isolation.</p>
<p><strong>Consider what actually happens in production:</strong></p>
<p><strong>The Problems:</strong></p>
<ul>
<li class="">
<p><strong>CRD conflicts:</strong> Team A deploys a service mesh with cluster-wide CRDs. Team B already has a different version of those CRDs. The conflict causes Team B's applications to fail during deployment. No way to isolate cluster-scoped resources.</p>
</li>
<li class="">
<p><strong>Version lock-in:</strong> You need to upgrade to Kubernetes 1.30 for a security patch. Team C has a critical application that breaks on 1.30. You can't upgrade safely, but you can't stay on a vulnerable version either.</p>
</li>
<li class="">
<p><strong>Scheduler conflicts:</strong> Team D wants a custom scheduler for GPU workloads. Team E needs the default scheduler. There's only one scheduler per cluster.</p>
</li>
<li class="">
<p><strong>Blast radius:</strong> A platform team member accidentally installs a broken admission webhook. It impacts every namespace in the cluster. All deployments across all teams start failing. Debugging requires coordinating across 15 teams.</p>
</li>
</ul>
<p><strong>The financial impact is significant:</strong></p>
<p>According to a <a href="https://www.the20.com/blog/the-cost-of-it-downtime/" target="_blank" rel="noopener noreferrer" class="">2024 ITIC study</a>, <mark>98% of organizations report that a single hour of downtime costs over $100,000</mark>, with the average reaching <a href="https://gatling.io/blog/the-cost-of-downtime" target="_blank" rel="noopener noreferrer" class="">$9,000 per minute</a> across industries. When a shared control plane issue affects multiple teams simultaneously, these costs multiply rapidly.</p>
<p>Kubernetes namespaces provide resource isolation, but they don't provide control plane isolation. Teams share the same API server, scheduler, controllers, and admission webhooks. This creates operational constraints that don't scale.</p>
<p>The alternative? Spin up separate clusters per team. But now you're managing dozens of physical clusters—each with its own control plane, nodes, and operational overhead. Expensive and operationally complex.</p>
<p>Platform teams needed better isolation without multiplying management overhead.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="virtual-clusters-lightweight-kubernetes-clusters">Virtual Clusters: Lightweight Kubernetes Clusters<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#virtual-clusters-lightweight-kubernetes-clusters" class="hash-link" aria-label="Direct link to Virtual Clusters: Lightweight Kubernetes Clusters" title="Direct link to Virtual Clusters: Lightweight Kubernetes Clusters" translate="no">​</a></h2>
<p>This is where we circle back to the beginning. Remember how VMs solved the problem of stranded resources on physical hardware? The same insight applies here.</p>
<p><a href="https://www.vcluster.com/" target="_blank" rel="noopener noreferrer" class="">vCluster</a> runs Kubernetes control plane components (API server, controller manager, and optionally a scheduler) inside a single pod on a host cluster. A syncer component bridges the virtual and host clusters, translating resources between them. The virtual cluster stores its state in SQLite or etcd, completely isolated from the host.</p>
<p>From the tenant's perspective, they have a full Kubernetes cluster:</p>
<p><strong>How It Solves The Problems:</strong></p>
<ul>
<li class="">
<p><strong>Independent upgrades:</strong> Each team controls their Kubernetes version. Team A tests 1.30 while Team B stays on 1.28. No coordination required.</p>
</li>
<li class="">
<p><strong>Flexible service sharing:</strong> Teams choose what to share:</p>
<ul>
<li class=""><strong>Fully shared:</strong> Use the host cluster's ingress controller and cert-manager</li>
<li class=""><strong>Partially shared:</strong> Share some services, run others independently within the vCluster</li>
<li class=""><strong>Fully isolated:</strong> Run all services independently within the vCluster</li>
</ul>
</li>
<li class="">
<p><strong>Flexible compute sharing:</strong> Teams choose how to share infrastructure:</p>
<ul>
<li class=""><strong>Shared nodes:</strong> Multiple vClusters share the same host cluster nodes for cost efficiency</li>
<li class=""><strong>Private nodes:</strong> Teams can join dedicated nodes directly to their vCluster for workloads requiring complete isolation (GPU training, compliance requirements)</li>
<li class=""><strong>Hybrid:</strong> Mix shared and private nodes based on workload needs</li>
</ul>
</li>
<li class="">
<p><strong>True control plane isolation:</strong> Each vCluster has its own API server, scheduler, and controllers. A broken admission webhook in vCluster A doesn't affect vCluster B.</p>
</li>
</ul>
<p><img decoding="async" loading="lazy" alt="What It Is" src="https://frosty-babbage-3125a3.netlify.app/assets/images/06-what-it-is-1ccdf015cfc6836d4ab6ac7544bd1576.png" width="3136" height="972" class="img_ev3q"></p>
<p>The cost efficiency is dramatic. You run dozens of vClusters on a single host cluster, sharing node capacity efficiently. One platform engineer can serve hundreds of teams without managing hundreds of physical clusters.</p>
<p><strong>Recent developments have expanded the flexibility:</strong></p>
<ul>
<li class=""><strong><a href="https://www.vcluster.com/blog/vnode-kubernetes-node-isolation-multi-tenancy" target="_blank" rel="noopener noreferrer" class="">Private Nodes</a></strong> (v0.27) let teams join dedicated nodes directly to their vCluster for workloads requiring complete isolation—like GPU training jobs or compliance-sensitive applications.</li>
<li class=""><strong><a href="https://www.vcluster.com/blog/introducing-vcluster-auto-nodes-karpenter-based-dynamic-autoscaling-anywhere" target="_blank" rel="noopener noreferrer" class="">Auto Nodes</a></strong> (v0.28) bring Karpenter-based elastic scaling to any infrastructure, giving teams dynamic compute without manual node management.</li>
<li class=""><strong><a href="https://www.vcluster.com/blog/vcluster-standalone-multi-tenancy-kubernetes" target="_blank" rel="noopener noreferrer" class="">vCluster Standalone</a></strong> (v0.29) removes the dependency on a host cluster entirely, allowing installation directly on bare metal or VMs.</li>
</ul>
<p><mark>You can tailor isolation to your needs:</mark> shared nodes for cost efficiency, private nodes for GPU workloads or security requirements, or standalone for complete infrastructure control.</p>
<p>Each abstraction solved a real problem. Each created a new one.</p>
<p>vClusters solve multi-tenancy but add another abstraction layer. Debugging requires understanding both vCluster and host cluster layers. Networking configuration becomes more complex. Permissions need careful mapping between vCluster and host resources.</p>
<p><strong>But the trade-off is favorable</strong> if you need multi-tenant Kubernetes at scale. The cost savings and operational simplification outweigh the added complexity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pattern">The Pattern<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#the-pattern" class="hash-link" aria-label="Direct link to The Pattern" title="Direct link to The Pattern" translate="no">​</a></h2>
<p><strong>You see the progression:</strong></p>
<ul>
<li class=""><strong>Physical computers → VMs</strong> (resources were stranded)</li>
<li class=""><strong>VMs → Containers</strong> (OS overhead was too heavy)</li>
<li class=""><strong>Containers → Kubernetes</strong> (orchestration was chaos)</li>
<li class=""><strong>Kubernetes → vClusters</strong> (multi-tenancy was broken)</li>
</ul>
<mark>Each abstraction solved a real problem. Each created a new one. This is how abstraction works.</mark>
<p>You trade one kind of complexity for another. You move problems up the stack. Physical constraints become logical constraints. Hardware limitations become software limitations.</p>
<p>The question is never "should we abstract?" The question is "what complexity are we willing to trade?"</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="try-it-yourself">Try It Yourself<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#try-it-yourself" class="hash-link" aria-label="Direct link to Try It Yourself" title="Direct link to Try It Yourself" translate="no">​</a></h2>
<p>If you want hands-on experience with vClusters, try the <a href="https://killercoda.com/decoder/course/vcluster/vcluster_introduction" target="_blank" rel="noopener noreferrer" class="">Killercoda interactive tutorial</a> I used at the workshop. You'll see exactly how vClusters provide isolation, how they integrate with host clusters, and what the operational model looks like.</p>
<p>Understanding the history and first principles makes the technology click. You'll see vClusters not as "yet another tool" but as the logical next step in a progression that started with physical computers six decades ago.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="resources">Resources<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/23/computing-abstraction-ladder#resources" class="hash-link" aria-label="Direct link to Resources" title="Direct link to Resources" translate="no">​</a></h2>
<p><strong>Learn More:</strong></p>
<ul>
<li class=""><a href="https://www.vcluster.com/docs" target="_blank" rel="noopener noreferrer" class="">vCluster Documentation</a></li>
<li class=""><a href="https://killercoda.com/decoder/course/vcluster/vcluster_introduction" target="_blank" rel="noopener noreferrer" class="">Killercoda Interactive Tutorial</a> - The hands-on workshop from KCD UK</li>
<li class=""><a href="https://github.com/kubernetes-sigs/multi-tenancy" target="_blank" rel="noopener noreferrer" class="">Kubernetes Multi-Tenancy Working Group</a></li>
</ul>
<p><strong><a href="https://cloudrumble.net/talks" target="_blank" rel="noopener noreferrer" class="">Watch My Talks</a>:</strong></p>
<ul>
<li class=""><a href="https://www.youtube.com/watch?v=sfGyBZmNxqk" target="_blank" rel="noopener noreferrer" class="">Flexible Multi-Tenancy with vCluster</a> - SREDay London 2025 (<a href="https://sreday.com/2025-london-q3/" target="_blank" rel="noopener noreferrer" class="">event page</a>)</li>
<li class=""><a href="https://www.youtube.com/watch?v=88Yeik-SagQ" target="_blank" rel="noopener noreferrer" class="">The Future of Kubernetes Tenancy</a> - Webinar with vCluster CEO</li>
</ul>
<p><strong>Events:</strong></p>
<ul>
<li class=""><a href="https://community.cncf.io/events/details/cncf-kcd-uk-presents-kubernetes-community-days-uk-edinburgh-2025/" target="_blank" rel="noopener noreferrer" class="">KCD UK Edinburgh 2025</a> - Where this blog idea was born</li>
</ul>
<hr>
<p>Every abstraction in computing history emerged to solve a real problem. Virtual clusters are no different. If you're running multi-tenant Kubernetes at scale, dealing with version conflicts, or managing dozens of teams with different requirements, <mark>vClusters offer the control plane isolation that namespaces can't provide</mark>.</p>
<p>Start with the <a href="https://killercoda.com/decoder/course/vcluster/vcluster_introduction" target="_blank" rel="noopener noreferrer" class="">interactive tutorial</a>, explore the <a href="https://www.vcluster.com/docs" target="_blank" rel="noopener noreferrer" class="">documentation</a>, and see how virtual clusters can transform your Kubernetes multi-tenancy strategy.</p>]]></content:encoded>
            <category>kubernetes</category>
            <category>virtualization</category>
            <category>containers</category>
            <category>infrastructure</category>
            <category>vcluster</category>
            <category>multi-tenancy</category>
        </item>
        <item>
            <title><![CDATA[Stop AI from Hallucinating Your Kubernetes YAML]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation</guid>
            <pubDate>Sat, 18 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Building a Deterministic vCluster Validation MCP Server to Ground AI in Real Schemas]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="AI and Kubernetes Configuration" src="https://frosty-babbage-3125a3.netlify.app/assets/images/vcluster-hero-39b29fd90220ff8046d13fec7dc8b0c7.png" width="1536" height="1024" class="img_ev3q"></p>
<style>
article img:not(:first-of-type) {
  max-width: 700px;
  height: auto;
}
</style>
<p><strong>Building a Deterministic vCluster Validation MCP Server to Ground AI in Real Schemas</strong></p>
<p>You ask an AI to generate a Kubernetes manifest, Helm chart values, or Ansible playbook. It responds instantly with clean, well-formatted YAML. You apply it. Nothing works.</p>
<p>This isn't a bug—it's <strong><a href="https://en.wikipedia.org/wiki/Hallucination_(artificial_intelligence)" target="_blank" rel="noopener noreferrer" class="">AI hallucination</a></strong>. The AI knows YAML syntax but hallucinates config options that don't exist, mixes incompatible versions, or confidently suggests deprecated fields. It generates what looks right based on patterns, not what <strong>is</strong> right according to actual schemas.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-cost-of-hallucinated-configs">The Cost of Hallucinated Configs<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#the-cost-of-hallucinated-configs" class="hash-link" aria-label="Direct link to The Cost of Hallucinated Configs" title="Direct link to The Cost of Hallucinated Configs" translate="no">​</a></h2>
<p>AI hallucinations aren't just inconvenient—they're expensive. From <a href="https://www.businessinsider.com/increasing-ai-hallucinations-fake-citations-court-records-data-2025-5" target="_blank" rel="noopener noreferrer" class="">legal briefs with fake citations</a> to financial analyses based on invented metrics, hallucinated AI content causes real damage.</p>
<p>For infrastructure teams, the pattern is consistent: generate config, apply it, watch it fail, spend 30-60 minutes debugging what the AI made up.</p>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Impact</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Cost</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Legal AI hallucination rate (top models)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100">6.4%</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Platform engineer time debugging AI configs</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100">20-30%</span></td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Companies using generative AI</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100">71%</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Google Bard hallucination incident (single day)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100">$100B market cap loss</span></td></tr></tbody></table></div>
<p><strong>The result?</strong> Teams stop trusting AI for infrastructure work. Productivity gains vanish. You're back to grep-ing through values.yaml files manually.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="making-ai-reliable-for-vcluster-configs">Making AI Reliable for vCluster Configs<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#making-ai-reliable-for-vcluster-configs" class="hash-link" aria-label="Direct link to Making AI Reliable for vCluster Configs" title="Direct link to Making AI Reliable for vCluster Configs" translate="no">​</a></h2>
<p><a href="https://www.vcluster.com/docs/vcluster/" target="_blank" rel="noopener noreferrer" class="">vCluster</a> is an open-source solution that enables teams to run virtual Kubernetes clusters inside existing infrastructure. These virtual clusters are Certified Kubernetes Distributions that provide strong workload isolation while running as nested environments on top of another Kubernetes cluster.</p>
<p><strong>Why vCluster configs are hard for AI:</strong></p>
<ul>
<li class="">Multiple major versions with different schemas (v0.19.0 vs v0.24.0)</li>
<li class="">Complex nested YAML structure (Helm chart values)</li>
<li class="">Validation rules hidden in comments, not schemas</li>
<li class="">Frequent deprecations and field migrations</li>
<li class="">Version-specific enum values</li>
</ul>
<p><strong>What AI hallucinates:</strong></p>
<ul>
<li class="">Config options that don't exist in your version</li>
<li class="">Deprecated fields that were renamed</li>
<li class="">Invalid enum values</li>
<li class="">Incompatible option combinations</li>
</ul>
<p>Example: <code>controlPlane.backingStore.etcd.deploy</code> moved to <code>controlPlane.backingStore.etcd.embedded</code> in v0.20.0. AI trained on mixed docs will confidently suggest the wrong path for your version.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution-deterministic-validation">The Solution: Deterministic Validation<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#the-solution-deterministic-validation" class="hash-link" aria-label="Direct link to The Solution: Deterministic Validation" title="Direct link to The Solution: Deterministic Validation" translate="no">​</a></h2>
<p>Stop hallucinations by <strong>grounding AI in actual schemas with deterministic validation</strong>.</p>
<p>I built an <a href="https://github.com/Piotr1215/vcluster-yaml-mcp" target="_blank" rel="noopener noreferrer" class="">MCP server</a> that connects any AI assistant—Claude, ChatGPT, or any MCP-compatible tool—directly to vCluster's GitHub repository. Every query, every validation, every config generation is grounded in the real Helm values for the exact version you specify. The pattern applies to any complex infrastructure configuration—vCluster is just the implementation.</p>
<p><img decoding="async" loading="lazy" alt="The Solution: Deterministic Validation" src="https://frosty-babbage-3125a3.netlify.app/assets/images/01-the-solution-deterministic-validation-72d67e2b1be7fa01f62552f159842942.png" width="1824" height="1816" class="img_ev3q"></p>
<p><strong>How it works:</strong></p>
<ol>
<li class="">Fetches configs directly from <code>github.com/loft-sh/vcluster</code> (source of truth)</li>
<li class="">Every tool accepts explicit version params (<code>v0.24.0</code>, <code>main</code>, etc.)</li>
<li class="">Validates against actual schemas—no pattern matching</li>
<li class="">15-minute smart cache (respects GitHub API limits)</li>
<li class="">Dual interface: MCP (for AI) + CLI (for humans)</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-model-context-protocol">Why Model Context Protocol?<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#why-model-context-protocol" class="hash-link" aria-label="Direct link to Why Model Context Protocol?" title="Direct link to Why Model Context Protocol?" translate="no">​</a></h2>
<p><a href="https://modelcontextprotocol.io/" target="_blank" rel="noopener noreferrer" class="">Model Context Protocol (MCP)</a> is an open standard created by Anthropic for connecting AI assistants to external data sources and tools. Instead of building custom integrations for each AI, MCP provides a universal interface.</p>
<p><strong>What makes MCP powerful:</strong></p>
<p>The <a href="https://modelcontextprotocol.io/specification/2025-03-26" target="_blank" rel="noopener noreferrer" class="">MCP specification</a> defines a client-server architecture where:</p>
<ul>
<li class=""><strong>MCP Clients</strong> (AI applications) can connect to any MCP server</li>
<li class=""><strong>MCP Servers</strong> expose tools, resources, and prompts to AI assistants</li>
<li class=""><strong>Transport layer</strong> supports stdio, HTTP, and Server-Sent Events (SSE)</li>
</ul>
<p>This means one MCP server works everywhere:</p>
<div class="info-card-grid" style="grid-template-columns:repeat(2, 1fr)"><div class="info-card"><div class="info-card-header">Claude Desktop</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Status<!-- -->:</strong> <span>Native Support</span></div></div></div><div class="info-card"><div class="info-card-header">Claude Web/Code</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Status<!-- -->:</strong> <span>Native Support</span></div></div></div><div class="info-card"><div class="info-card-header">ChatGPT Desktop</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Status<!-- -->:</strong> <span>Officially Adopted (March 2025)</span></div></div></div><div class="info-card"><div class="info-card-header">Any MCP-Compatible AI</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Status<!-- -->:</strong> <span>Open Standard</span></div></div></div></div>
<p><strong>Build once, use everywhere.</strong> No custom integrations. No vendor lock-in.</p>
<p>The vCluster YAML server is both an MCP server (for AI) and a standalone CLI tool (for humans and automation). Full architecture details in the <a href="https://github.com/Piotr1215/vcluster-yaml-mcp" target="_blank" rel="noopener noreferrer" class="">GitHub repo</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="three-features-that-matter">Three Features That Matter<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#three-features-that-matter" class="hash-link" aria-label="Direct link to Three Features That Matter" title="Direct link to Three Features That Matter" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="no-more-version-drift">No More Version Drift<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#no-more-version-drift" class="hash-link" aria-label="Direct link to No More Version Drift" title="Direct link to No More Version Drift" translate="no">​</a></h3>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Query specific versions</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vcluster-yaml query </span><span class="token string" style="color:rgb(255, 121, 198)">"sync.fromHost"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"> v0.19.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vcluster-yaml query </span><span class="token string" style="color:rgb(255, 121, 198)">"sync.fromHost"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"> v0.24.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># AI can query multiple versions in parallel</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># No state conflicts, no version switching</span><br></span></code></pre></div></div>
<p>Your AI assistant now knows <strong>exactly</strong> which options exist in which version. No more mixing v0.19 examples with v0.24 configs.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="no-more-silent-failures">No More Silent Failures<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#no-more-silent-failures" class="hash-link" aria-label="Direct link to No More Silent Failures" title="Direct link to No More Silent Failures" translate="no">​</a></h3>
<p>When your AI generates a config, it <strong>validates before showing you</strong>:</p>
<p><strong>You ask:</strong></p>
<blockquote>
<p>"Create a vCluster config with HA etcd and node sync for v0.24"</p>
</blockquote>
<p><strong>Claude does:</strong></p>
<ol>
<li class="">Queries v0.24 schema</li>
<li class="">Generates YAML</li>
<li class=""><strong>Validates it automatically</strong></li>
<li class="">Shows you only validated output</li>
</ol>
<p><strong>What it looks like in practice:</strong></p>
<p>First, Claude catches the error:</p>
<p><img decoding="async" loading="lazy" alt="Claude catches validation error" src="https://frosty-babbage-3125a3.netlify.app/assets/images/claude-error-f940cd13b0635edfc69581a4e4186c5d.webp" width="1391" height="1233" class="img_ev3q"></p>
<p>Then provides the corrected, validated config:</p>
<p><img decoding="async" loading="lazy" alt="Claude provides validated fix" src="https://frosty-babbage-3125a3.netlify.app/assets/images/claude-fix-5c1dfbadab9d13284be5f6fa5709068f.webp" width="1341" height="1365" class="img_ev3q"></p>
<p>No copy-paste-debug loop. Every config works on first apply.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="no-more-context-switching">No More Context Switching<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#no-more-context-switching" class="hash-link" aria-label="Direct link to No More Context Switching" title="Direct link to No More Context Switching" translate="no">​</a></h3>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># storage: Valid options: "ephemeral", "persistent"</span><br></span></code></pre></div></div>
<p>The server extracts rules like enums, dependencies, and defaults—knowledge AI wouldn't have from schema alone.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="try-it-now-2-minutes">Try It Now (2 Minutes)<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#try-it-now-2-minutes" class="hash-link" aria-label="Direct link to Try It Now (2 Minutes)" title="Direct link to Try It Now (2 Minutes)" translate="no">​</a></h2>
<p>Works with Claude Desktop, Claude Web/Code, ChatGPT Desktop, or any MCP-compatible AI.</p>
<p><strong>Option 1: Local MCP</strong></p>
<p>Add to your AI's MCP configuration (example for Claude Desktop at <code>~/Library/Application Support/Claude/claude_desktop_config.json</code>):</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"mcpServers"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"vcluster-yaml"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"command"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"npx"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"args"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"-y"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"vcluster-yaml-mcp-server"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p><strong>Option 2: Remote (No Install)</strong></p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token property">"mcpServers"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token property">"vcluster-yaml"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"type"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"http"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token property">"url"</span><span class="token operator">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"https://vcluster-yaml.cloudrumble.net/mcp"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<p>Restart your AI client.</p>
<p><strong>Try these prompts:</strong></p>
<p><strong>1. Generate Validated Config</strong></p>
<blockquote>
<p>"Create a vCluster v0.24 config with HA etcd, ingress, and node sync"</p>
</blockquote>
<p><strong>2. Version Comparison</strong></p>
<blockquote>
<p>"Compare sync.fromHost between v0.19.0 and v0.24.0"</p>
</blockquote>
<p><strong>3. Fix Existing Config</strong></p>
<blockquote>
<p>"Validate this config against v0.24: [paste your YAML]"</p>
</blockquote>
<p>Watch your AI assistant query schemas, validate in real-time, and give you production-ready configs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="dual-interface-ai--cli">Dual Interface: AI + CLI<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#dual-interface-ai--cli" class="hash-link" aria-label="Direct link to Dual Interface: AI + CLI" title="Direct link to Dual Interface: AI + CLI" translate="no">​</a></h2>
<p>The server provides two ways to access the same validation engine:</p>
<p><strong>1. MCP Protocol</strong> (for AI assistants)
Connect any MCP-compatible AI to query schemas and validate configs interactively.</p>
<p><strong>2. Standalone CLI</strong> (for humans and automation)
Direct command-line access. No AI needed. Perfect for CI/CD pipelines and quick validations:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Quick validation</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vcluster-yaml validate my-config.yaml </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"> v0.24.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Pipe from stdin</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cat</span><span class="token plain"> config.yaml </span><span class="token operator">|</span><span class="token plain"> vcluster-yaml validate -</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># CI/CD integration</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vcluster-yaml validate vcluster.yaml </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--version</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">${VCLUSTER_VERSION}</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--quiet</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$?</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-eq</span><span class="token plain"> </span><span class="token number">0</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exit</span><span class="token plain"> </span><span class="token number">1</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Batch validation</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token for-or-select variable" style="color:rgb(189, 147, 249);font-style:italic">f</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> configs/*.yaml</span><span class="token punctuation" style="color:rgb(248, 248, 242)">;</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">do</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  vcluster-yaml validate </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$f</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">||</span><span class="token plain"> </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"Failed: </span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$f</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">done</span><br></span></code></pre></div></div>
<p>Shell completion, JSON/table output, and designed for automation.</p>
<p>Full CLI docs: <a href="https://github.com/Piotr1215/vcluster-yaml-mcp/blob/main/docs/CLI.md" target="_blank" rel="noopener noreferrer" class="">github.com/Piotr1215/vcluster-yaml-mcp</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="available-mcp-tools">Available MCP Tools<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#available-mcp-tools" class="hash-link" aria-label="Direct link to Available MCP Tools" title="Direct link to Available MCP Tools" translate="no">​</a></h2>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Tool</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Purpose</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Version Aware</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">list-versions</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Get all available vCluster versions (tags/branches)</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">N/A</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">smart-query</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Query config options by dot notation or natural language</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Yes</span></td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">create-vcluster-config</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Generate and validate configs in one step</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Yes</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">validate-config</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Validate existing YAML against specific version</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Yes</span></td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">extract-validation-rules</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Extract enums, constraints from YAML comments</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Yes</span></td></tr></tbody></table></div>
<p><strong>All tools accept explicit <code>--version</code> parameter. Query multiple versions in parallel without state conflicts.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="architecture">Architecture<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#architecture" class="hash-link" aria-label="Direct link to Architecture" title="Direct link to Architecture" translate="no">​</a></h2>
<p>The server is built on four principles that make it reliable for production use:</p>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Principle</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Description</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Stateless</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Every query takes explicit version params. Query v0.19 and v0.24 in parallel—no conflicts, no state drift.</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Source-of-Truth</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Fetches directly from loft-sh/vcluster GitHub. No manual updates. Always reflects actual source.</td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Token-Optimized</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">kubectl-style output, not JSON dumps. 800-1500 tokens vs 2K+.</td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Production-Ready</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">15-min smart cache, HTTP/SSE transport, comprehensive tests, Docker container available.</td></tr></tbody></table></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="beyond-vcluster">Beyond vCluster<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#beyond-vcluster" class="hash-link" aria-label="Direct link to Beyond vCluster" title="Direct link to Beyond vCluster" translate="no">​</a></h2>
<p>This pattern works for any config-heavy infra tool:</p>
<ul>
<li class="">Terraform modules</li>
<li class="">Kubernetes operators</li>
<li class="">Helm charts</li>
<li class="">Cloud provider configs</li>
</ul>
<p>The question isn't whether AI will help manage infrastructure. It's <strong>how we build the grounding layer that makes it reliable</strong>.</p>
<p>This is that layer for vCluster. What could it be for your tools?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="get-started">Get Started<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/18/ai-grounded-vcluster-yaml-validation#get-started" class="hash-link" aria-label="Direct link to Get Started" title="Direct link to Get Started" translate="no">​</a></h2>
<p><strong>Resources:</strong></p>
<ul>
<li class=""><a href="https://github.com/Piotr1215/vcluster-yaml-mcp" target="_blank" rel="noopener noreferrer" class="">GitHub repo</a></li>
<li class=""><a href="https://www.npmjs.com/package/vcluster-yaml-mcp-server" target="_blank" rel="noopener noreferrer" class="">npm package</a></li>
<li class=""><a href="https://github.com/Piotr1215/vcluster-yaml-mcp/blob/main/docs/CLI.md" target="_blank" rel="noopener noreferrer" class="">CLI docs</a></li>
<li class="">Public instance: <code>vcluster-yaml.cloudrumble.net/mcp</code></li>
</ul>
<p><strong>Try it:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># CLI only</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">npx </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-p</span><span class="token plain"> vcluster-yaml-mcp-server vcluster-yaml query </span><span class="token function" style="color:rgb(80, 250, 123)">sync</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># With AI</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Add MCP config above, restart your AI client (Claude, ChatGPT, etc.)</span><br></span></code></pre></div></div>
<p><strong>Contribute:</strong></p>
<ul>
<li class=""><a href="https://github.com/Piotr1215/vcluster-yaml-mcp/issues" target="_blank" rel="noopener noreferrer" class="">Issues</a></li>
<li class=""><a href="https://github.com/Piotr1215/vcluster-yaml-mcp/discussions" target="_blank" rel="noopener noreferrer" class="">Discussions</a></li>
<li class="">PRs welcome</li>
</ul>
<hr>
<p>Try it. Let me know what breaks. Star it if it saves you an hour of debugging.</p>]]></content:encoded>
            <category>kubernetes</category>
            <category>vcluster</category>
            <category>ai</category>
            <category>mcp</category>
            <category>claude</category>
            <category>yaml</category>
            <category>devops</category>
            <category>hallucination</category>
        </item>
        <item>
            <title><![CDATA[How to Run Multiple GPU KAI Schedulers in Kubernetes Using vCluster]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler</guid>
            <pubDate>Fri, 10 Oct 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[Kubernetes Clusters]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="Kubernetes Clusters" src="https://frosty-babbage-3125a3.netlify.app/assets/images/vcluster-kai-scheduler-hero-a82bfb610da82fb5a7f35fad599bbe73.jpg" width="2057" height="1157" class="img_ev3q"></p>
<p><small>Photo by <a href="https://unsplash.com/@growtika?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Growtika</a> on <a href="https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></small></p>
<p>In today's cloud-native landscape, GPU workloads are becoming increasingly critical. From training large language models to running inference APIs, organizations are investing heavily in GPU infrastructure. But with this investment comes a challenge: how do you safely test and deploy new GPU schedulers without risking your entire production environment?</p>
<blockquote>
<p><strong>Related talks:</strong> Watch my <a href="https://www.youtube.com/watch?v=JpdYrwi-33M" target="_blank" rel="noopener noreferrer" class="">SREDay Paris Q4 2025 talk</a> on this topic. Also presenting at <a href="https://www.conf42.com/kubenative2025" target="_blank" rel="noopener noreferrer" class="">Conf42 Kube Native 2025</a>. Check the <a class="" href="https://frosty-babbage-3125a3.netlify.app/docs/talks">talks page</a> for more details.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-gpu-scheduling-challenge">The GPU Scheduling Challenge<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#the-gpu-scheduling-challenge" class="hash-link" aria-label="Direct link to The GPU Scheduling Challenge" title="Direct link to The GPU Scheduling Challenge" translate="no">​</a></h2>
<p>Let me paint a picture of what most teams face today. You're running a Kubernetes cluster with precious GPU resources. Multiple teams depend on these GPUs for everything from model training to real-time inference. Your current scheduler works, but you've heard about NVIDIA's KAI Scheduler and its promise of fractional GPU allocation and better resource utilization.</p>
<p>The problem? Testing a new scheduler in production is like performing surgery on yourself - one mistake and everything stops working.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="understanding-gpu-workloads">Understanding GPU Workloads<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#understanding-gpu-workloads" class="hash-link" aria-label="Direct link to Understanding GPU Workloads" title="Direct link to Understanding GPU Workloads" translate="no">​</a></h2>
<p>Before we dive into the solution, let's understand what actually runs on GPUs in modern infrastructure:</p>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Workload Type</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Real-World Examples</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">GPU Utilization</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Model Training</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Fine-tuning LLMs, Deep Learning</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">100% for hours/days</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Stable Diffusion</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Image generation services</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">~50% GPU</span></td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">LLM Inference</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">ChatGPT-like APIs</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">25-75% depending on model</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Video Processing</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Transcoding, streaming</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Variable 20-80%</span></td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">CUDA Development</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Jupyter notebooks, testing</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Often &lt; 20%</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Batch Processing</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Scientific computing</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Spikes to 100%</span></td></tr></tbody></table></div>
<p>Notice something? Most workloads don't use 100% of a GPU all the time. Yet traditional Kubernetes scheduling treats GPUs as indivisible resources. This is where KAI Scheduler shines - but how do you test it safely?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-nvidia-kai-scheduler">What is NVIDIA KAI Scheduler?<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#what-is-nvidia-kai-scheduler" class="hash-link" aria-label="Direct link to What is NVIDIA KAI Scheduler?" title="Direct link to What is NVIDIA KAI Scheduler?" translate="no">​</a></h2>
<p>In January 2025, NVIDIA open-sourced their KAI (Kubernetes AI) Scheduler, bringing enterprise-grade GPU management to the community. It's an advanced Kubernetes scheduler designed specifically for GPU workload optimization.</p>
<p><strong>Key capabilities:</strong></p>
<div class="info-card-grid" style="grid-template-columns:repeat(2, 1fr)"><div class="info-card"><div class="info-card-header">Fractional GPU allocation</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Benefit<!-- -->:</strong> <span>Share single GPU between workloads</span></div></div></div><div class="info-card"><div class="info-card-header">Queue-based scheduling</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Benefit<!-- -->:</strong> <span>Hierarchical resource management</span></div></div></div><div class="info-card"><div class="info-card-header">Topology awareness</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Benefit<!-- -->:</strong> <span>Optimize for hardware layout</span></div></div></div><div class="info-card"><div class="info-card-header">Fair sharing</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Benefit<!-- -->:</strong> <span>Prevent resource monopolization</span></div></div></div></div>
<p>As a smart traffic controller for your GPUs, KAI ensures maximum utilization without causing collisions.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-production-scheduler-dilemma">The Production Scheduler Dilemma<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#the-production-scheduler-dilemma" class="hash-link" aria-label="Direct link to The Production Scheduler Dilemma" title="Direct link to The Production Scheduler Dilemma" translate="no">​</a></h2>
<p>Here's the reality of upgrading schedulers in production:</p>
<p><strong>Current challenges:</strong></p>
<ul>
<li class="">Single scheduler controls entire cluster</li>
<li class="">Any changes affect all workloads</li>
<li class="">No isolation between teams</li>
<li class="">Rollback procedures take hours</li>
</ul>
<p><strong>The impact:</strong></p>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Failure Mode</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Impact</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Recovery Time</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Business Cost</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Scheduler bug</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">All pods pending</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">2-4 hours</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100">High</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">CRD conflicts</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Namespace corruption</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">6+ hours</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100">Critical</span></td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Version mismatch</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Random pod failures</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">1-2 days</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100">Very High</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Resource leak</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">GPU exhaustion</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">4-8 hours</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-red-100 dark:bg-red-900 text-red-800 dark:text-red-100">Critical</span></td></tr></tbody></table></div>
<p>According to New Relic's 2024 data, enterprise downtime costs between $100k-1M+ per hour. Can you afford to take that risk?</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="solution-vcluster-for-isolated-testing">Solution: vCluster for Isolated Testing<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#solution-vcluster-for-isolated-testing" class="hash-link" aria-label="Direct link to Solution: vCluster for Isolated Testing" title="Direct link to Solution: vCluster for Isolated Testing" translate="no">​</a></h2>
<p>vCluster creates a fully functional Kubernetes cluster inside a namespace of your existing cluster. It's not a new EKS cluster or GKE cluster - it's a virtual cluster running inside your current infrastructure.</p>
<p><strong>Key characteristics:</strong></p>
<p><img decoding="async" loading="lazy" alt="Solution: vCluster for Isolated Testing" src="https://frosty-babbage-3125a3.netlify.app/assets/images/01-solution-vcluster-for-isolated-testing-f76c296c55882bd7635a83fd52fae520.png" width="3136" height="1688" class="img_ev3q"></p>
<p>The architecture consists of these components:</p>
<ul>
<li class=""><strong>API Server</strong>: Handles all Kubernetes API calls independently</li>
<li class=""><strong>Syncer</strong>: Bi-directional resource synchronization with host</li>
<li class=""><strong>SQLite/etcd</strong>: Complete state isolation</li>
<li class=""><strong>Virtual Scheduler</strong>: Independent scheduling decisions</li>
</ul>
<p>This architecture enables running a Kubernetes cluster inside Kubernetes, with complete isolation but shared underlying resources.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-syncer-vclusters-core-component">The Syncer: vCluster's Core Component<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#the-syncer-vclusters-core-component" class="hash-link" aria-label="Direct link to The Syncer: vCluster's Core Component" title="Direct link to The Syncer: vCluster's Core Component" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="The Syncer: vCluster&amp;#39;s Core Component" src="https://frosty-babbage-3125a3.netlify.app/assets/images/02-the-syncer-vclusters-core-component-ec46a9021b194a8b84cc72aca40c55a4.png" width="3136" height="468" class="img_ev3q"></p>
<p>The syncer is the component that makes vCluster work seamlessly. It's responsible for:</p>
<ul>
<li class="">Synchronizing resources between virtual and host cluster</li>
<li class="">Translating virtual resources to host resources</li>
<li class="">Managing resource lifecycle</li>
<li class="">Ensuring isolation boundaries</li>
</ul>
<p>This means your GPU workloads scheduled by KAI inside the vCluster actually run on real GPU nodes in your host cluster, but all scheduling decisions are isolated.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-solution-isolated-testing-with-vcluster">The Solution: Isolated Testing with vCluster<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#the-solution-isolated-testing-with-vcluster" class="hash-link" aria-label="Direct link to The Solution: Isolated Testing with vCluster" title="Direct link to The Solution: Isolated Testing with vCluster" translate="no">​</a></h2>
<p>Here's how you can safely test KAI Scheduler without risking production:</p>
<p><img decoding="async" loading="lazy" alt="vCluster Isolation" src="https://frosty-babbage-3125a3.netlify.app/assets/images/03-solution-vcluster-isolation-76a84994f92d4f49f22ab1b3e8aa9024.png" width="2604" height="2160" class="img_ev3q"></p>
<p><strong>The workflow:</strong></p>
<ol>
<li class="">Create a vCluster with virtual scheduler enabled</li>
<li class="">Install KAI Scheduler inside the vCluster</li>
<li class="">Deploy test workloads with fractional GPU requests</li>
<li class="">Observe behavior in complete isolation</li>
<li class="">If something fails? Delete the vCluster in 30 seconds</li>
</ol>
<p><strong>Benefits achieved:</strong></p>
<div class="my-6 overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm"><table class="min-w-full divide-y divide-gray-200 dark:divide-gray-700"><thead class="bg-gray-100 dark:bg-gray-800 border-b-2 border-gray-300 dark:border-gray-600"><tr><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Capability</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Time Saved</th><th class="px-6 py-4 text-left text-xs font-bold text-gray-800 dark:text-gray-200 uppercase tracking-wider whitespace-nowrap">Risk Reduced</th></tr></thead><tbody class="bg-white dark:bg-gray-900 divide-y divide-gray-200 dark:divide-gray-700"><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Test scheduler upgrades</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-100">4 hours → 5 min</span></td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">100% → 0%</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Rollback bad changes</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-100">2 hours → 30 sec</span></td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Critical → None</span></td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">A/B test versions</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-100">Not possible → Easy</span></td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">High → Zero</span></td></tr><tr class="hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">Per-team schedulers</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-100">Days → Minutes</span></td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">Complex → Simple</span></td></tr><tr class="bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-700/50 transition-colors"><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap">GPU sharing validation</td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-100">Weeks → Hours</span></td><td class="px-6 py-4 text-sm text-gray-900 dark:text-gray-100 whitespace-nowrap"><span class="px-3 py-1 text-xs font-semibold rounded-full inline-block whitespace-nowrap bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-100">High → None</span></td></tr></tbody></table></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="supporting-multiple-teams">Supporting Multiple Teams<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#supporting-multiple-teams" class="hash-link" aria-label="Direct link to Supporting Multiple Teams" title="Direct link to Supporting Multiple Teams" translate="no">​</a></h2>
<p>Consider this scenario: Your ML team wants to test KAI v0.9.3 for its new features, while your Research team requires the stable v0.7.11 version. With traditional approaches, teams must coordinate, wait, and compromise on a single version.</p>
<p>With vCluster, each team operates their own virtual cluster with their own KAI scheduler version, providing complete autonomy without interference.</p>
<p><strong>Parallel scheduler deployments:</strong></p>
<div class="info-card-grid" style="grid-template-columns:repeat(3, 1fr)"><div class="info-card"><div class="info-card-header">team-ml</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Scheduler Version<!-- -->:</strong> <span class="info-card-badge info-card-badge-info">KAI v0.9.3</span></div><div><strong class="text-gray-700 dark:text-gray-400">Purpose<!-- -->:</strong> <span>Testing new features</span></div></div></div><div class="info-card"><div class="info-card-header">team-research</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Scheduler Version<!-- -->:</strong> <span class="info-card-badge info-card-badge-info">KAI v0.7.11</span></div><div><strong class="text-gray-700 dark:text-gray-400">Purpose<!-- -->:</strong> <span>Stable version</span></div></div></div><div class="info-card"><div class="info-card-header">team-dev</div><div class="info-card-content"><div><strong class="text-gray-700 dark:text-gray-400">Scheduler Version<!-- -->:</strong> <span class="info-card-badge info-card-badge-info">Default scheduler</span></div><div><strong class="text-gray-700 dark:text-gray-400">Purpose<!-- -->:</strong> <span>Standard workloads</span></div></div></div></div>
<p><strong>Architecture benefits:</strong></p>
<ul>
<li class=""><strong>Virtual Scheduler</strong>: ENABLED in each vCluster</li>
<li class=""><strong>KAI Location</strong>: Inside each vCluster</li>
<li class=""><strong>Scheduling</strong>: Independent per team</li>
<li class=""><strong>Host Impact</strong>: NONE</li>
<li class=""><strong>Isolation</strong>: COMPLETE</li>
</ul>
<p>Each team can iterate at their own pace, test different configurations, and only promote to production when they're confident.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="real-world-impact">Real-World Impact<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#real-world-impact" class="hash-link" aria-label="Direct link to Real-World Impact" title="Direct link to Real-World Impact" translate="no">​</a></h2>
<p>Based on typical enterprise deployment scenarios, here's what you can achieve:</p>
<p><strong>Time savings:</strong></p>
<ul>
<li class="">Setup to first test: <strong>5 minutes</strong> instead of 4+ hours</li>
<li class="">Version switching: <strong>30 seconds</strong> instead of 2+ hours</li>
<li class="">Team onboarding: <strong>Minutes</strong> instead of days</li>
</ul>
<p><strong>Risk reduction:</strong></p>
<ul>
<li class="">Blast radius: <strong>Single namespace</strong> instead of entire cluster</li>
<li class="">Rollback complexity: <strong>Delete command</strong> instead of complex procedures</li>
<li class="">Testing freedom: <strong>Complete</strong> instead of severely limited</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started">Getting Started<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#getting-started" class="hash-link" aria-label="Direct link to Getting Started" title="Direct link to Getting Started" translate="no">​</a></h2>
<p>Want to try this approach? I've created a complete hands-on guide with all the technical details, configurations, and scripts you need:</p>
<p><strong>Technical Resources:</strong></p>
<ul>
<li class=""><a href="https://github.com/Piotr1215/youtube/blob/main/kai-scheduler/presentation.md" target="_blank" rel="noopener noreferrer" class="">Complete Setup Guide</a> - Step-by-step instructions for deploying vCluster with KAI Scheduler</li>
<li class=""><a href="https://github.com/Piotr1215/youtube/tree/main/kai-scheduler" target="_blank" rel="noopener noreferrer" class="">GitHub Repository</a> - All configuration files, scripts, and examples</li>
</ul>
<p>The guide includes:</p>
<ul>
<li class="">vCluster configuration with virtual scheduler</li>
<li class="">KAI Scheduler installation</li>
<li class="">Sample GPU workloads with fractional allocation</li>
<li class="">Multi-team setup examples</li>
<li class="">Troubleshooting tips</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="closing-thoughts">Closing Thoughts<a href="https://frosty-babbage-3125a3.netlify.app/blog/2025/10/10/vcluster-kai-scheduler#closing-thoughts" class="hash-link" aria-label="Direct link to Closing Thoughts" title="Direct link to Closing Thoughts" translate="no">​</a></h2>
<p>The combination of vCluster and NVIDIA KAI Scheduler represents a paradigm shift in how we can approach GPU workload management in Kubernetes. Instead of choosing between innovation and stability, you can have both.</p>
<p>vCluster provides the safety net that enables rapid experimentation. KAI Scheduler provides the advanced GPU management capabilities modern workloads demand. Together, they enable you to:</p>
<ul>
<li class="">Test scheduler upgrades without fear</li>
<li class="">Give teams autonomy over their GPU scheduling</li>
<li class="">Maximize GPU utilization through fractional allocation</li>
<li class="">Reduce operational complexity and risk</li>
</ul>
<p>The question isn't whether you should adopt this approach - it's what you'll build once you're no longer held back by fear of breaking production.</p>
<p>What GPU scheduling challenges are you facing? How could vCluster help your team move faster?</p>]]></content:encoded>
            <category>kubernetes</category>
            <category>vcluster</category>
            <category>gpu</category>
            <category>nvidia</category>
            <category>kai-scheduler</category>
            <category>devops</category>
        </item>
        <item>
            <title><![CDATA[Hijacking Unix Commands: How to Intercept and Modify Program Behavior]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense</guid>
            <pubDate>Sat, 02 Aug 2025 00:00:00 GMT</pubDate>
            <description><![CDATA[PATH Interception Flow Diagram]]></description>
            <content:encoded><![CDATA[<p><img decoding="async" loading="lazy" alt="PATH Interception Flow Diagram" src="https://frosty-babbage-3125a3.netlify.app/assets/images/path-hijacking-flow-0b638b98799209f136095cb18fbd32bf.png" width="1024" height="1024" class="img_ev3q"></p>
<p>This technique is useful when a program you can't modify calls CLI tools on your machine. For example, <code>pet search</code> pipes output directly to <code>fzf</code> with no filtering options. PATH hijacking lets you intercept and modify this behavior without changing either program.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-problem-that-started-it-all">The Problem That Started It All<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#the-problem-that-started-it-all" class="hash-link" aria-label="Direct link to The Problem That Started It All" title="Direct link to The Problem That Started It All" translate="no">​</a></h2>
<p>I love using <a href="https://github.com/knqyf263/pet" target="_blank" rel="noopener noreferrer" class="">pet</a> for managing command snippets, but every search shows these <code>[Link to: ...]</code> entries mixed with actual commands. Since <code>pet</code> pipes directly to <code>fzf</code>, traditional solutions like aliases or pipes won't work. That's when we discovered we could intercept the entire data flow by hijacking the PATH.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-path-hijacking-works">How PATH Hijacking Works<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#how-path-hijacking-works" class="hash-link" aria-label="Direct link to How PATH Hijacking Works" title="Direct link to How PATH Hijacking Works" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="diagram" src="https://frosty-babbage-3125a3.netlify.app/assets/images/path-hijacking-simple-13830bed5850da8589d483e918797a81.png" width="917" height="500" class="img_ev3q"></p>
<p>Unix has a beautifully simple rule for finding commands: it searches directories in PATH from left to right and uses the first match. We can exploit this:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token assign-left variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">PATH</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"/tmp/my-tools:/usr/local/bin:/usr/bin:/bin"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      ↑              ↑</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      Our fake fzf   Real fzf</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">found first</span><span class="token operator">!</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">never reached</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>The magic happens because programs inherit their parent's environment, including PATH. When <code>pet</code> calls <code>fzf</code>, it finds our wrapper instead!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-not-just-use-a-function-or-alias">Why Not Just Use a Function or Alias?<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#why-not-just-use-a-function-or-alias" class="hash-link" aria-label="Direct link to Why Not Just Use a Function or Alias?" title="Direct link to Why Not Just Use a Function or Alias?" translate="no">​</a></h2>
<p>You might think: "Why not just create a shell function?"</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># This function works when YOU type 'fzf' in your shell</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">function</span><span class="token plain"> </span><span class="token function-name function" style="color:rgb(80, 250, 123)">fzf</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token function" style="color:rgb(80, 250, 123)">grep</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-v</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'^\[Link to'</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> /usr/local/bin/fzf </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$@</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># But when 'pet' internally executes 'fzf', it ignores your function!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">pet search  </span><span class="token comment" style="color:rgb(98, 114, 164)"># Still shows [Link to] entries - function is ignored</span><br></span></code></pre></div></div>
<p><strong>Functions and aliases only work in your shell session.</strong> When programs like <code>pet</code> call other programs, they use system calls (like <code>execve()</code>) that bypass shell constructs entirely. They search PATH directly for the binary.</p>
<p>PATH hijacking works because:</p>
<ul>
<li class="">Every program inherits environment variables from its parent</li>
<li class="">The OS always searches PATH when executing commands</li>
<li class="">Your wrapper is found before the real binary</li>
</ul>
<p><strong>Use functions for your own commands, PATH hijacking for intercepting other programs' behavior.</strong></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="solving-the-pet-snippets-problem">Solving the Pet Snippets Problem<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#solving-the-pet-snippets-problem" class="hash-link" aria-label="Direct link to Solving the Pet Snippets Problem" title="Direct link to Solving the Pet Snippets Problem" translate="no">​</a></h2>
<p>Here's the complete solution to filter unwanted entries:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token shebang important">#!/usr/bin/env bash</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># pet-search-clean - Filter pet search output</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Create temporary directory for our wrapper</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">WRAPPER_DIR</span><span class="token operator">=</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">mktemp </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-d</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Create fake fzf that filters input</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">cat</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$WRAPPER_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">/fzf"</span><span class="token plain"> </span><span class="token operator">&lt;&lt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'EOF'</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">#!/usr/bin/env bash</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"># Remove '[Link to' entries before passing to real fzf</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">grep -v '^\[Link to' | /usr/local/bin/fzf "$@"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EOF</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">chmod</span><span class="token plain"> +x </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$WRAPPER_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">/fzf"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Hijack PATH temporarily</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">export</span><span class="token plain"> </span><span class="token assign-left variable environment constant" style="color:rgb(189, 147, 249);font-style:italic">PATH</span><span class="token operator">=</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$WRAPPER_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">:</span><span class="token string environment constant" style="color:rgb(189, 147, 249)">$PATH</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Run pet search - it'll use our wrapper!</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">pet search</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token comment" style="color:rgb(98, 114, 164)"># Clean up</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token function" style="color:rgb(80, 250, 123)">rm</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-rf</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$WRAPPER_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><br></span></code></pre></div></div>
<p>Save this as <code>pet-search-clean</code> and enjoy clutter-free snippet browsing!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="advanced-techniques">Advanced Techniques<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#advanced-techniques" class="hash-link" aria-label="Direct link to Advanced Techniques" title="Direct link to Advanced Techniques" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="conditional-interception">Conditional Interception<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#conditional-interception" class="hash-link" aria-label="Direct link to Conditional Interception" title="Direct link to Conditional Interception" translate="no">​</a></h3>
<p>Only intercept under certain conditions:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">cat</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$WRAPPER_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">/docker"</span><span class="token plain"> </span><span class="token operator">&lt;&lt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'EOF'</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">#!/usr/bin/env bash</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"># Add --rm flag to 'docker run' if not present</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">if [[ "$1" == "run" ]] &amp;&amp; ! echo "$@" | grep -q -- --rm; then</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    /usr/bin/docker run --rm "${@:2}"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">else</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">    /usr/bin/docker "$@"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">fi</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EOF</span><br></span></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="chaining-multiple-filters">Chaining Multiple Filters<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#chaining-multiple-filters" class="hash-link" aria-label="Direct link to Chaining Multiple Filters" title="Direct link to Chaining Multiple Filters" translate="no">​</a></h3>
<p>Process data through multiple transformations:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">cat</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$WRAPPER_DIR</span><span class="token string" style="color:rgb(255, 121, 198)">/fzf"</span><span class="token plain"> </span><span class="token operator">&lt;&lt;</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'EOF'</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">#!/usr/bin/env bash</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)"># Multiple filters in sequence</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">sed 's/old/new/g' |       # Replace text</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">grep -v '^#' |            # Remove comments  </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">sort -u |                 # Remove duplicates</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">/usr/local/bin/fzf "$@"</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token string" style="color:rgb(255, 121, 198)">EOF</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="best-practices--common-pitfalls">Best Practices &amp; Common Pitfalls<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#best-practices--common-pitfalls" class="hash-link" aria-label="Direct link to Best Practices &amp; Common Pitfalls" title="Direct link to Best Practices &amp; Common Pitfalls" translate="no">​</a></h2>
<table><thead><tr><th>Do</th><th>Don't</th><th>Why</th></tr></thead><tbody><tr><td>Use <code>mktemp -d</code> for wrapper directories</td><td>Use fixed paths like <code>/tmp/wrapper</code></td><td>Avoid conflicts between scripts</td></tr><tr><td>Specify full paths to real binaries</td><td>Call commands without paths</td><td>Prevents infinite recursion</td></tr><tr><td>Clean up with <code>rm -rf</code> immediately</td><td>Leave wrapper directories around</td><td>Security and cleanliness</td></tr><tr><td>Pass <code>"$@"</code> to preserve arguments</td><td>Forget command arguments</td><td>Maintains original functionality</td></tr><tr><td>Add <code>chmod +x</code> to wrapper scripts</td><td>Skip permissions</td><td>Scripts won't execute</td></tr><tr><td>Test with <code>echo</code> first</td><td>Deploy untested wrappers</td><td>Catch errors early</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="security-note">Security Note<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#security-note" class="hash-link" aria-label="Direct link to Security Note" title="Direct link to Security Note" translate="no">​</a></h2>
<p>While incredibly useful, PATH hijacking can be dangerous. Always:</p>
<ul>
<li class="">Use absolute paths for sensitive commands (<code>/usr/bin/sudo</code>)</li>
<li class="">Check your PATH regularly with <code>echo $PATH</code></li>
<li class="">Be cautious with downloaded scripts that modify PATH</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://frosty-babbage-3125a3.netlify.app/blog/unix-path-hijacking-defense#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>PATH hijacking transforms impossible filtering problems into trivial solutions. It's a perfect example of Unix philosophy: simple mechanisms that compose into powerful tools. This technique provides a way to add missing features to existing command-line tools without modifying their source code.</p>
<p>Try the pet search filter and experiment with your own wrappers. Once you understand this technique, you'll see opportunities everywhere to enhance your command-line workflow.</p>]]></content:encoded>
            <category>unix</category>
            <category>bash</category>
            <category>cli</category>
            <category>fzf</category>
            <category>productivity</category>
        </item>
        <item>
            <title><![CDATA[typeit-nvim]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim</guid>
            <pubDate>Thu, 04 Jul 2024 18:10:07 GMT</pubDate>
            <description><![CDATA[title: Typing Simulation in Neovim with typeit.nvim]]></description>
            <content:encoded><![CDATA[<p>title: Typing Simulation in Neovim with typeit.nvim
description: Typing simulation in Neovim with typeit.nvim
date: 2024-07-04
tags: [neovim,plugin ]
hide_table_of_contents: false
<img decoding="async" loading="lazy" alt="Photo by Andrew
Seaman
on
Unsplash" src="https://frosty-babbage-3125a3.netlify.app/assets/images/580689_image0-f557550af3cb45a91ece411f9742916c.jpg" width="1000" height="750" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="neovim-plugin-useful-for-technical-presentations-from-a-terminal">Neovim plugin useful for technical presentations from a terminal<a href="https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim#neovim-plugin-useful-for-technical-presentations-from-a-terminal" class="hash-link" aria-label="Direct link to Neovim plugin useful for technical presentations from a terminal" title="Direct link to Neovim plugin useful for technical presentations from a terminal" translate="no">​</a></h2>
<h1>Introduction</h1>
<p>As a developer who frequently gives technical presentations and demos
from the terminal, I’ve always been drawn to Neovim’s extensibility.
It’s a powerful feature, especially for those of us creating
tutorials, demos, or presentations. That’s why I created <code>typeit.nvim</code>.</p>
<p>Over the years, I’ve found that live coding during presentations can be
risky — typos, mistakes, and the pressure of an audience can sometimes
lead to less-than-smooth demonstrations. On the other hand, pre-recorded
videos or static code snippets often lack the dynamism that keeps an
audience engaged. I needed something in between, and that’s where the
idea for <code>typeit.nvim</code> was born.</p>
<p>I leveraged Neovim’s extensibility to create a plugin that allows
presenters to simulate typing in real-time, complete with customizable
typing speed. Whether you’re creating tutorials, giving live demos, or
just want to add some flair to your coding screencasts, <code>typeit.nvim</code>
can help bring your code to life.</p>
<p>In this blog post, I’ll guide you through the features, installation,
configuration, and usage of <code>typeit.nvim</code>. By the end, you'll have a new
tool in your Neovim arsenal for creating more engaging and realistic
coding demonstrations, enhancing your Neovim experience.</p>
<h1>Prerequisites</h1>
<p>Before diving into the installation and setup of <code>typeit.nvim</code>, ensure
you have the following:</p>
<ul>
<li class="">Neovim version 0.9.0 or higher</li>
<li class="">A plugin manager such as, <code>packer.nvim</code>, or
<code>lazy.nvim</code></li>
</ul>
<h1>Installation</h1>
<p>You can install <code>typeit.nvim</code> using various plugin managers. Below are
the instructions for the three popular options:</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="using-packernvim">Using <a href="https://github.com/wbthomason/packer.nvim" target="_blank" rel="noopener noreferrer" class="">packer.nvim</a><a href="https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim#using-packernvim" class="hash-link" aria-label="Direct link to using-packernvim" title="Direct link to using-packernvim" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">use </span><span class="token string" style="color:rgb(255, 121, 198)">'Piotr1215/typeit.nvim'</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="using-lazynvim">Using <a href="https://github.com/folke/lazy.nvim" target="_blank" rel="noopener noreferrer" class="">lazy.nvim</a><a href="https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim#using-lazynvim" class="hash-link" aria-label="Direct link to using-lazynvim" title="Direct link to using-lazynvim" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    </span><span class="token string" style="color:rgb(255, 121, 198)">'Piotr1215/typeit.nvim'</span><span class="token plain">,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    config </span><span class="token operator">=</span><span class="token plain"> function</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        require</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'typeit'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">.setup</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">            -- Your configuration here</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    end</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h1>Configuration</h1>
<p>After installation, you can configure <code>typeit.nvim</code> globally using the
<code>setup</code> function. Here’s a basic example:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">require</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'typeit'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">.setup</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    default_speed </span><span class="token operator">=</span><span class="token plain"> </span><span class="token number">30</span><span class="token plain">,    -- Default typing speed </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">milliseconds</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    default_pause </span><span class="token operator">=</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">'line'</span><span class="token plain"> -- Default pause behavior </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'line'</span><span class="token plain"> or </span><span class="token string" style="color:rgb(255, 121, 198)">'paragraph'</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<h1>Usage</h1>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="vim-commands">Vim Commands<a href="https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim#vim-commands" class="hash-link" aria-label="Direct link to Vim Commands" title="Direct link to Vim Commands" translate="no">​</a></h2>
<p><code>typeit.nvim</code> provides several commands for simulating typing in Neovim:</p>
<ul>
<li class=""><code>:SimulateTyping [file_path] [speed]</code>: Simulate
typing from a file</li>
<li class=""><code>:SimulateTypingWithPauses [file_path] [speed] [pause_at]</code>: Simulate typing with pauses (‘line’ or
‘paragraph’)</li>
<li class=""><code>:StopTyping</code>: Stop the current typing
simulation</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="simulating-typing-from-a-file">Simulating Typing from a File<a href="https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim#simulating-typing-from-a-file" class="hash-link" aria-label="Direct link to Simulating Typing from a File" title="Direct link to Simulating Typing from a File" translate="no">​</a></h2>
<p>To simulate typing the contents of a file:</p>
<ol>
<li class="">Open a new empty buffer: <code>:enew</code></li>
<li class="">Use the <code>SimulateTyping</code> command:</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">:SimulateTyping ~/example.txt </span><span class="token number">30</span><br></span></code></pre></div></div>
<p>This command simulates typing the contents of <code>example.txt</code> at a speed
of 30 milliseconds per character.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="simulating-typing-with-pauses">Simulating Typing with Pauses<a href="https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim#simulating-typing-with-pauses" class="hash-link" aria-label="Direct link to Simulating Typing with Pauses" title="Direct link to Simulating Typing with Pauses" translate="no">​</a></h2>
<p>To simulate typing with pauses between lines or paragraphs:</p>
<ol>
<li class="">Open a new empty buffer: <code>:enew</code></li>
<li class="">Use the <code>SimulateTypingWithPauses</code> command:</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">:SimulateTypingWithPauses ~/example.txt </span><span class="token number">50</span><span class="token plain"> line</span><br></span></code></pre></div></div>
<p>This command pauses after each line at a typing speed of 50 milliseconds
per character. For paragraph pauses, use:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">:SimulateTypingWithPauses ~/example.txt </span><span class="token number">50</span><span class="token plain"> paragraph</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="simulating-custom-text-typing">Simulating Custom Text Typing<a href="https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim#simulating-custom-text-typing" class="hash-link" aria-label="Direct link to Simulating Custom Text Typing" title="Direct link to Simulating Custom Text Typing" translate="no">​</a></h2>
<p>You can also simulate typing custom text directly in Neovim:</p>
<ol>
<li class="">Open a new empty buffer: <code>:enew</code></li>
<li class="">Enter command mode and type your text in
quotes:</li>
</ol>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">:call luaeval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"require('typeit').simulate_typing(_A[1], _A[2])"</span><span class="token plain">, </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"This is a custom text being typed out."</span><span class="token plain">, </span><span class="token number">40</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>This command simulates typing “This is a custom text being typed out.”
at a speed of 40 milliseconds per character.</p>
<p>For custom text with pauses:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">:call luaeval</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">"require('typeit').simulate_typing_with_pauses(_A[1], _A[2], _A[3])"</span><span class="token plain">, </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"Line 1</span><span class="token string entity" style="color:rgb(255, 121, 198)">\n</span><span class="token string" style="color:rgb(255, 121, 198)">Line 2</span><span class="token string entity" style="color:rgb(255, 121, 198)">\n</span><span class="token string" style="color:rgb(255, 121, 198)">Line 3"</span><span class="token plain">, </span><span class="token string" style="color:rgb(255, 121, 198)">"line"</span><span class="token plain">, </span><span class="token number">30</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>This simulates typing the given lines with pauses after each line at a
speed of 30 milliseconds per character.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="stopping-the-simulation">Stopping the Simulation<a href="https://frosty-babbage-3125a3.netlify.app/blog/typeit-nvim#stopping-the-simulation" class="hash-link" aria-label="Direct link to Stopping the Simulation" title="Direct link to Stopping the Simulation" translate="no">​</a></h2>
<p>To stop the typing simulation at any point, use:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">:StopTyping</span><br></span></code></pre></div></div>
<p>Alternatively, you can use <code>Ctrl+C</code> to interrupt the typing simulation.</p>
<h1>Custom Keybindings</h1>
<p>Set up custom keybindings for <code>typeit.nvim</code> commands:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">vim.api.nvim_set_keymap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'n'</span><span class="token plain">, </span><span class="token string" style="color:rgb(255, 121, 198)">'&lt;leader&gt;st'</span><span class="token plain">, </span><span class="token string" style="color:rgb(255, 121, 198)">':SimulateTyping&lt;CR&gt;'</span><span class="token plain">, </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> noremap </span><span class="token operator">=</span><span class="token plain"> true, silent </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">vim.api.nvim_set_keymap</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token string" style="color:rgb(255, 121, 198)">'n'</span><span class="token plain">, </span><span class="token string" style="color:rgb(255, 121, 198)">'&lt;leader&gt;sp'</span><span class="token plain">, </span><span class="token string" style="color:rgb(255, 121, 198)">':SimulateTypingWithPauses&lt;CR&gt;'</span><span class="token plain">, </span><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"> noremap </span><span class="token operator">=</span><span class="token plain"> true, silent </span><span class="token operator">=</span><span class="token plain"> </span><span class="token boolean">true</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<h1>Conclusion</h1>
<p><code>typeit.nvim</code> is a versatile plugin that brings dynamic typing
simulations to Neovim, making it perfect for live demos, tutorials, and
presentations. By integrating this plugin into your workflow, you can
create more engaging content and showcase your coding skills in
real-time.</p>
<p>Thanks for taking the time to read this post. I hope you found it
interesting and informative.</p>
<p>🔗 <strong>Connect with me on</strong>
<a href="https://www.linkedin.com/in/piotr-zaniewski/" target="_blank" rel="noopener noreferrer" class=""><strong>LinkedIn</strong></a></p>
<p>📺 <strong>Subscribe to my</strong> <a href="https://www.youtube.com/@cloud-native-corner" target="_blank" rel="noopener noreferrer" class=""><strong>YouTube
Channel</strong></a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[from-silos-to-synergy]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/from-silos-to-synergy</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/from-silos-to-synergy</guid>
            <pubDate>Wed, 26 Jun 2024 17:11:27 GMT</pubDate>
            <description><![CDATA[title Cloud Infrastructure Management in the Age of Platform Teams"]]></description>
            <content:encoded><![CDATA[<p>title: "From Silos to Synergy: Cloud Infrastructure Management in the Age of Platform Teams"
date: 2024-06-26
tags: ['platform', 'iac']
<img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/289487_image0-b43944f770c31eddb5e114be459d5e29.jpg" width="1000" height="563" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="navigating-lifecycle-and-ownership-in-modern-infrastructure-architectures">Navigating Lifecycle and Ownership in Modern Infrastructure Architectures<a href="https://frosty-babbage-3125a3.netlify.app/blog/from-silos-to-synergy#navigating-lifecycle-and-ownership-in-modern-infrastructure-architectures" class="hash-link" aria-label="Direct link to Navigating Lifecycle and Ownership in Modern Infrastructure Architectures" title="Direct link to Navigating Lifecycle and Ownership in Modern Infrastructure Architectures" translate="no">​</a></h2>
<h1>Introduction</h1>
<p>Software and the infrastructure it needs, have a complicated
relationship. On one hand, a web app will need somewhere to run, but
where it runs and how it gets there might vary.</p>
<p>Infrastructure and the software it serves can have different
<strong>lifecycle</strong> and <strong>ownership</strong>. A microservice with a database is a
good example of shared lifecycle and ownership. Different lifecycles are
when the same microservice requires a message broker like Kafka. An
example of different lifecycle and ownership? A microservice might need
a key-value store while a centralized security team manages it.</p>
<p>The resulting architecture is typically more complicated than a <code>hallo world</code> IaC example.</p>
<p><img decoding="async" loading="lazy" alt="Applications/Infrastructure with different lifecycles and
ownerhsip" src="https://frosty-babbage-3125a3.netlify.app/assets/images/289487_image2-0e8609f673cfbd568cd538602ffbcadb.jpg" width="1000" height="671" class="img_ev3q"></p>
<h1>Why does it matter?</h1>
<p><img decoding="async" loading="lazy" alt="Photo by Oyemike
Princewill
on
Unsplash" src="https://frosty-babbage-3125a3.netlify.app/assets/images/289487_image3-bbf005db8e7b2027520643639e0b10b2.jpg" width="700" height="600" class="img_ev3q"></p>
<p>Platform Teams are responsible for maintaining all the shared
infrastructure. An excellent book, <em>“Team Toplogies”</em> claims.</p>
<blockquote>
<p><em>“Platform teams produce and maintain the infrastructure and services
that all of your teams use to communicate with each other and perform
tasks. Examples of these kinds of services include your internal
security tools, remote work applications, cloud storage solutions, and
internal network design.”​</em></p>
</blockquote>
<p>I would like to argue that <em>it depends</em>. Depends on lifecycle and
ownership. Remember the phrase <em>“You build it, you run it”</em> attributed
to Werner Vogels, the CTO of Amazon? Death to silos and long live 2
pizza teams. It’s all great, if not always practical. Moreover, blindly
following this rule might get us in trouble.</p>
<h1>Silos are not bad</h1>
<p>On the contrary, silos are amazing, when implemented right. Can you
imagine being involved in the decisions of how to run your EKS cluster
or Google Cloud Run?</p>
<p>Well, it’s your cluster and your container, so … run it. Thankfully,
that’s not how it works. Cloud providers offer a <strong>self-service</strong>
options and otherwise get out of the way. For all intents and purposes,
they are <em>silos</em> where we throw our workloads over the fence and that’s
it.</p>
<p>Cloud hyperscalers came up with guidelines that capture differences in
ownership and lifecycle. They are called shared responsibility
guidelines.</p>
<h1>Rethinking Ownership in Tech</h1>
<p>The tech industry’s approach to ownership and lifecycle management
continues to evolve. While Werner Vogels’ <em>“You build it, you run it”</em>
philosophy pushed for end-to-end ownership, reality often demands more
nuance.</p>
<p>As Sam Newman points out in “Building Microservices”</p>
<blockquote>
<p>“Microservices give us options in terms of how we implement our
systems, but they don’t dictate the organizational structures we use.”</p>
</blockquote>
<p>This applies to ownership models as well.</p>
<p>Different components typically have different lifecycles and ownership
needs. A rapidly iterating microservice might be fully owned by a
product team, while a shared database could be managed by a platform
team. The key is finding the right balance for each organization’s
unique needs.</p>
<blockquote>
<p>“Good architecture allows major decisions to be deferred.” — Robert C.
Martin</p>
</blockquote>
<p>This flexibility in architecture should extend to our ownership models,
allowing them to adapt as our systems and organizations grow and change.</p>
<h1>Architecture Recommendations</h1>
<p><img decoding="async" loading="lazy" alt="Photo by Alex
wong
on
Unsplash" src="https://frosty-babbage-3125a3.netlify.app/assets/images/289487_image4-94e7010dd926c2d7738f81fdc1a7bfee.jpg" width="700" height="467" class="img_ev3q"></p>
<p>In modern software development and infrastructure management, various
techniques and tools have emerged to address the challenges of different
lifecycle and ownership models. These include service discovery portals,
self-service platforms, GitOps practices, Infrastructure as Code (IaC),
and internal developer platforms (IDPs).</p>
<p>While these techniques are valuable, the underlying organizational
architecture is crucial for their effective implementation. Here is an
overview of organizational architecture models addressing each
lifecycle/ownership permutation.</p>
<table><thead><tr><th>Lifecycle</th><th>Ownership</th><th>Architecture/Organization Model</th></tr></thead><tbody><tr><td>Application-bound</td><td>Developers</td><td>"You build it, you run it" model</td></tr><tr><td>Application-bound</td><td>External team</td><td>Embedded platform engineers</td></tr><tr><td>Shared</td><td>Developers</td><td>Complicated subsystem team</td></tr><tr><td>Shared</td><td>External team</td><td>Platform team approach</td></tr></tbody></table>
<p>Based on the analysis of lifecycle and ownership patterns, I recommend
the following architectural approaches:</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-you-build-it-you-run-it-model">1. “You build it, you run it” model<a href="https://frosty-babbage-3125a3.netlify.app/blog/from-silos-to-synergy#1-you-build-it-you-run-it-model" class="hash-link" aria-label="Direct link to 1. “You build it, you run it” model" title="Direct link to 1. “You build it, you run it” model" translate="no">​</a></h2>
<p><strong>Context</strong>: Application-bound infrastructure owned by developers</p>
<p>This model, popularized by Amazon, empowers development teams with full
responsibility for their services, including infrastructure. It
promotes:</p>
<ul>
<li class="">End-to-end ownership and accountability</li>
<li class="">Rapid iteration and deployment</li>
<li class="">Deep understanding of both application and
infrastructure needs</li>
</ul>
<p>Implementation often involves extensive use of cloud services,
Infrastructure as Code, and robust CI/CD pipelines. Teams in this model
benefit from self-service platforms and comprehensive monitoring tools.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-embedded-platform-engineers">2. Embedded platform engineers<a href="https://frosty-babbage-3125a3.netlify.app/blog/from-silos-to-synergy#2-embedded-platform-engineers" class="hash-link" aria-label="Direct link to 2. Embedded platform engineers" title="Direct link to 2. Embedded platform engineers" translate="no">​</a></h2>
<p><strong>Context</strong>: Application-bound infrastructure owned by an external team</p>
<p>This approach bridges the gap between specialized infrastructure
knowledge and application-specific needs. Key aspects include:</p>
<ul>
<li class="">Close collaboration between platform experts and
development teams</li>
<li class="">Tailored infrastructure solutions that align with
application requirements</li>
<li class="">Knowledge transfer and upskilling of development
teams</li>
</ul>
<p>Successful implementation often involves creating service catalogs,
implementing GitOps practices, and establishing clear communication
channels between platform engineers and developers.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-complicated-subsystem-team">3. Complicated subsystem team<a href="https://frosty-babbage-3125a3.netlify.app/blog/from-silos-to-synergy#3-complicated-subsystem-team" class="hash-link" aria-label="Direct link to 3. Complicated subsystem team" title="Direct link to 3. Complicated subsystem team" translate="no">​</a></h2>
<p><strong>Context</strong>: Shared infrastructure owned by developers</p>
<p>This model, derived from Team Topologies, is suitable for managing
complex, shared components that require deep expertise. Characteristics
include:</p>
<ul>
<li class="">Focused team of specialists managing a critical,
shared subsystem</li>
<li class="">Clear interfaces and APIs for other teams to
interact with the subsystem</li>
<li class="">Continuous evolution and optimization of the shared
component</li>
</ul>
<p>Implementation might involve creating comprehensive documentation,
establishing service level objectives (SLOs), and developing
self-service interfaces for other teams to utilize the subsystem.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-platform-team-approach">4. Platform team approach<a href="https://frosty-babbage-3125a3.netlify.app/blog/from-silos-to-synergy#4-platform-team-approach" class="hash-link" aria-label="Direct link to 4. Platform team approach" title="Direct link to 4. Platform team approach" translate="no">​</a></h2>
<p><strong>Context</strong>: Shared infrastructure owned by an external team</p>
<p>This model centralizes the management of shared infrastructure,
providing a foundation for other teams to build upon. Key features
include:</p>
<ul>
<li class="">Dedicated team focusing on creating and maintaining
shared infrastructure</li>
<li class="">Emphasis on creating self-service capabilities for
development teams</li>
<li class="">Standardization of infrastructure practices across
the organization</li>
</ul>
<p>Successful platform teams frequently implement internal developer
platforms (IDPs), use Infrastructure as Code for managing resources, and
create service discovery portals to make their offerings easily
accessible to development teams.</p>
<h1>Closing Thoughts</h1>
<p>As we’ve explored throughout this discussion, the relationship between
software and infrastructure is complex and multifaceted. The
architectural approaches we’ve outlined — from “You build it, you run
it” to Platform teams — each align with specific lifecycle and
ownership patterns, providing a foundation for effective infrastructure
management.</p>
<p>It’s crucial to remember that there’s no one-size-fits-all solution. The
choice of approach should be based on your organization’s specific
needs, culture, and technical landscape. Factors such as team size,
technical expertise, regulatory requirements, and business objectives
all play a role in determining the most suitable architecture.</p>
<p>Moreover, it’s entirely possible — and often beneficial — for these
models to coexist within the same organization. Different components or
services may require different approaches. A critical, shared database
might be best managed by a complicated subsystem team, while a
customer-facing microservice could thrive under a “You build it, you run
it” model.</p>
<p>The key is to remain flexible and open to evolution. As your
organization grows and changes, so too should your architectural
approach. Regular reassessment of your infrastructure management
strategies can help ensure they continue to serve your needs
effectively.</p>
<p>Thanks for taking the time to read this post. I hope you found it
interesting and informative.</p>
<p>🔗 <strong>Connect with me on</strong>
<a href="https://www.linkedin.com/in/piotr-zaniewski/" target="_blank" rel="noopener noreferrer" class=""><strong>LinkedIn</strong></a></p>
<p>🌐 <strong>Visit my</strong> <a href="https://cloudrumble.net/" target="_blank" rel="noopener noreferrer" class=""><strong>Website</strong></a></p>
<p>📺 <strong>Subscribe to my</strong> <a href="https://www.youtube.com/@cloud-native-corner" target="_blank" rel="noopener noreferrer" class=""><strong>YouTube
Channel</strong></a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[5-must-have-ai-tools]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools</guid>
            <pubDate>Thu, 20 Jun 2024 19:24:09 GMT</pubDate>
            <description><![CDATA[title: "5 Must-Have Command Line AI Tools"]]></description>
            <content:encoded><![CDATA[<p>title: "5 Must-Have Command Line AI Tools"
date: 2024-06-20
tags: ['ai', 'command-line', 'automation', 'productivity']
<img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/984983_image0-06993c3c8fc0c0a9ce7c155e0072ae4c.jpg" width="1000" height="563" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="terminal-friendly-ai-projects">Terminal Friendly AI Projects<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#terminal-friendly-ai-projects" class="hash-link" aria-label="Direct link to Terminal Friendly AI Projects" title="Direct link to Terminal Friendly AI Projects" translate="no">​</a></h2>
<h1>Introduction</h1>
<p>Artificial Intelligence (AI) is not just a buzzword; it’s a
transformative force reshaping industries across the globe. The U.S. AI
market alone is projected to reach approximately $594 billion by 2032,
growing at a robust CAGR of 19% from 2023. This staggering growth
underscores AI’s pivotal role in driving innovation and efficiency.</p>
<p>If you’re not leveraging AI in your workflows yet, you might be missing
out on significant opportunities. AI is rapidly becoming a critical
component in staying competitive, and those who adopt AI tools now are
positioning themselves at the forefront of technological advancement.</p>
<p>In this blog, I would like to show you 5 tools that improved my
productivity and. You don’t need to be a software developer or IT
professional to take advantage of the same efficiencty boost.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="lets-look-at-some-statistics">Let’s look at some statistics<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#lets-look-at-some-statistics" class="hash-link" aria-label="Direct link to Let’s look at some statistics" title="Direct link to Let’s look at some statistics" translate="no">​</a></h2>
<blockquote>
<p>The U.S. AI market is expected to reach approximately $594 billion by
2032, with a CAGR of 19% from 2023​ (<a href="https://connect.comptia.org/blog/artificial-intelligence-statistics-facts" target="_blank" rel="noopener noreferrer" class="">Statistics and Facts for 2024
CompTIA</a>)​.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/984983_image2-a90fa1b35a71e76421fcfec77b911b5d.jpg" width="700" height="451" class="img_ev3q"></p>
<blockquote>
<p>Approximately 34% of companies are currently using AI, with an
additional 42% exploring AI technologies. This highlights a
significant interest and ongoing integration of AI in business
operations​ (<a href="https://connect.comptia.org/blog/artificial-intelligence-statistics-facts" target="_blank" rel="noopener noreferrer" class="">Statistics and Facts for 2024
CompTIA</a>)​.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/984983_image3-26771d7bd32d1401a45f0c627f38ffcf.jpg" width="700" height="447" class="img_ev3q"></p>
<blockquote>
<p>AI is projected to create 12 million more jobs than it will replace by
2025. The demand for AI specialists is anticipated to rise, with 97
million positions needed in the industry by that time​ (<a href="https://connect.comptia.org/blog/artificial-intelligence-statistics-facts" target="_blank" rel="noopener noreferrer" class="">Statistics
and Facts for 2024
CompTIA</a>)​.</p>
</blockquote>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/984983_image4-4fb386732b3cb6ef1dfc811691677cf0.jpg" width="700" height="371" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-the-terminal"><strong>Why the Terminal?</strong><a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#why-the-terminal" class="hash-link" aria-label="Direct link to why-the-terminal" title="Direct link to why-the-terminal" translate="no">​</a></h2>
<p>You are probably familiar with ChatGPT or Claude web interfaces and
those are great first steps to try out generative AI. However, those web
UIs have important limitations; they are generic and not tailored to
specific needs. While convenient, they lack the flexibility to integrate
seamlessly with custom workflows and automate repetitive tasks.</p>
<p>The command line is a powerful interface that offers more control,
efficiency and flexibility than graphical interfaces. It allows for
scripting, automation, and quick access to powerful tools without the
overhead of a graphical interface.</p>
<blockquote>
<p>AI is revolutionizing the way we interact with technology. By
integrating AI with command line tools, we can automate complex tasks,
gain deeper insights from data, and improve overall productivity.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="its-easier-than-you-think"><strong>It’s Easier Than You Think</strong><a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#its-easier-than-you-think" class="hash-link" aria-label="Direct link to its-easier-than-you-think" title="Direct link to its-easier-than-you-think" translate="no">​</a></h2>
<p>Using AI tools in the terminal is straightforward. Many tools provide
simple installation commands and detailed documentation to help you get
started quickly.</p>
<p>Command line tools often offer more granular control over their
operation, allowing you to customize your workflows to suit your
specific needs.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="better-automation">Better Automation<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#better-automation" class="hash-link" aria-label="Direct link to Better Automation" title="Direct link to Better Automation" translate="no">​</a></h2>
<p>Terminal-based AI tools excel at automation. They can be easily
integrated into shell scripts, scheduled with cron jobs, and used in
combination with other command line utilities to create powerful
automated workflows.</p>
<h1>Tools</h1>
<p>Before we jump into the tooling overview, let’s make sure we are on the
same page, defining what’s what in the terminal universe. Put simply:</p>
<p><img decoding="async" loading="lazy" alt="Terminal Related Definitions" src="https://frosty-babbage-3125a3.netlify.app/assets/images/984983_image5-7c5fd4f08885575ef4348316c142047b.jpg" width="700" height="764" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ollama">Ollama<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#ollama" class="hash-link" aria-label="Direct link to Ollama" title="Direct link to Ollama" translate="no">​</a></h2>
<p>A command-line tool developed my Meta, that allows you to run AI models
locally, enabling seamless and secure interactions with various LLMs
directly from your terminal. Chat or interact with AI models through
APIs on your local machine or a remote server in home network or
somewhere else.</p>
<ul>
<li class="">ollama/ollama: Get up and running with Llama 3, Mistral, Gemma, and other large language…</li>
</ul>
<p>🎥 See <code>ollama</code> in action on asciinema: <a href="https://asciinema.org/a/664871" target="_blank" rel="noopener noreferrer" class="">https://asciinema.org/a/664871</a></p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/984983_image6-63d07b42ee9279a06e1d4c2ea7c15cdc.jpg" width="700" height="380" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="fabric">Fabric<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#fabric" class="hash-link" aria-label="Direct link to Fabric" title="Direct link to Fabric" translate="no">​</a></h2>
<p>An open-source framework designed to augment human capabilities using
AI. It offers a modular system for solving specific problems through a
crowdsourced set of AI prompts, known as Patterns.</p>
<p>Fabric enables users to integrate AI into their daily tasks seamlessly,
from summarizing content to generating social media posts. It emphasizes
usability with a command-line interface and clear Markdown-based
patterns. The tool also supports creating personal AI infrastructures,
making it a versatile choice for both simple and complex AI
applications.</p>
<ul>
<li class="">danielmiessler/fabric: fabric is an open-source framework for augmenting humans using AI…</li>
</ul>
<p>However, we can do better than this and let <code>fabric</code> speak for itself
using its <code>— agent</code> flag:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"research the https://github.com/danielmiessler/fabric repository and summarize what the tool does"</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      </span><span class="token operator">|</span><span class="token plain"> fabric </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--agents</span><span class="token plain"> </span><span class="token operator">|</span><span class="token plain"> summarize</span><br></span></code></pre></div></div>
<p>This gives us the following response:</p>
<p><strong>ONE SENTENCE SUMMARY:</strong><br>
<!-- -->The fabric tool enhances productivity by automating tasks, integrating
with software, and offering features to streamline user workflows.</p>
<p><strong>MAIN POINTS:</strong><br>
<!-- -->1. The fabric tool is designed to improve productivity and streamline
tasks.<br>
<!-- -->2. It focuses on automation and simplification of repetitive tasks.<br>
<!-- -->3. Offers seamless integration with other software or services.<br>
<!-- -->4. Provides a suite of features aimed at enhancing process
efficiency.<br>
<!-- -->5. Direct consultation of the GitHub repository is recommended for full
potential leverage.<br>
<!-- -->6. The repository contains a comprehensive README file for detailed
insights.<br>
<!-- -->7. Additional documentation is available, prepared by the repository’s
owner.<br>
<!-- -->8. In-depth insights into the tool’s purpose and features are
provided.<br>
<!-- -->9. Setup instructions and practical use cases are included in the
documentation.<br>
<!-- -->10. Effective utilization requires exploring the GitHub repository for
maximum utility.</p>
<p><strong>TAKEAWAYS:</strong><br>
<!-- -->1. The fabric tool is pivotal for enhancing productivity through
automation and integration.<br>
<!-- -->2. Direct exploration of the GitHub repository is essential for
understanding its full capabilities.<br>
<!-- -->3. The README file and additional documentation are key resources for
users.<br>
<!-- -->4. The tool offers significant benefits in streamlining and improving
efficiency of tasks.<br>
<!-- -->5. Understanding and applying the tool’s features requires consulting
the provided documentation.</p>
<p><strong>Extract Transcript from YouTube Videos</strong></p>
<p>Fabric can also extract transcript from youtube videos and pass it
through any prompt(s). Let’s extract some ideas from one of my recent
videos</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">yt </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--transcript</span><span class="token plain"> https://www.youtube.com/watch</span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain">?v</span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token operator">=</span><span class="token plain">EK_ivK8HlNo </span><span class="token operator">|</span><span class="token plain"> create_micro_summary</span><br></span></code></pre></div></div>
<p><strong>ONE SENTENCE SUMMARY:</strong><br>
<!-- -->- Kubernetes development challenges are mitigated by MirrorD for faster
feedback loops and seamless remote environment testing.</p>
<p><strong>MAIN POINTS:</strong><br>
<!-- -->- Kubernetes excels in production but complicates development and
testing.<br>
<!-- -->- Fast feedback loops are crucial for efficient Kubernetes
development.<br>
<!-- -->- MirrorD enables local processes to run in a remote Kubernetes
context.</p>
<p><strong>TAKEAWAYS:</strong><br>
<!-- -->- Development on Kubernetes requires innovative tools for efficiency.<br>
<!-- -->- MirrorD shortens the feedback loop significantly.<br>
<!-- -->- Local and remote environment synchronization is key for developers.</p>
<p>The summary is pretty spot on!!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="shell-automation">Shell Automation<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#shell-automation" class="hash-link" aria-label="Direct link to Shell Automation" title="Direct link to Shell Automation" translate="no">​</a></h2>
<p>There are so many commands with flags and options that it’s impossible
to remember… thinking about you <code>ffmpeg</code> . To make this easier for
myself, I have created a tool that helps me with various commands.</p>
<ul>
<li class="">Piotr1215/aicmdtools: GoAI is a Go library and command line for interacting with OpenAI…</li>
</ul>
<p>🎥 Check out this <code>asciinema</code> recording to see what it can do:
<a href="https://asciinema.org/a/TFJABWbNochPWuuBFhiHDHBLF" target="_blank" rel="noopener noreferrer" class="">https://asciinema.org/a/TFJABWbNochPWuuBFhiHDHBLF</a></p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/984983_image7-a2c5a1d3af4c18515abca25e9370c908.jpg" width="700" height="380" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="terminal-chat">Terminal Chat<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#terminal-chat" class="hash-link" aria-label="Direct link to Terminal Chat" title="Direct link to Terminal Chat" translate="no">​</a></h2>
<p>Terminal chat apps are similar to <code>ollama</code> but allow for talking to
various LLM providers such as OpenAI, Claude and more. Here are two I’ve
experimented with and can recommend.</p>
<p>However, I usually use AI chats through <code>neovim</code>, more on it later in a
bonus section!</p>
<ul>
<li class="">
<p>sigoden/aichat: All-in-one AI CLI tool that integrates 20+ AI platforms, including OpenAI…</p>
</li>
<li class="">
<p>ekkinox/yai: Your AI powered terminal assistant.</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="aider--programming-support">Aider — Programming Support<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#aider--programming-support" class="hash-link" aria-label="Direct link to Aider — Programming Support" title="Direct link to Aider — Programming Support" translate="no">​</a></h2>
<p>If you are a programmer, aider is absolutely amazing. While it’s not
perfect, it’s the closest I know to GitHub Copilot (with chat function)
and it’s completely free!</p>
<p>Let’s see if fabric can help us capture the main strengths of aider.
Let’s paste the content or aider’s repo README.md and let fabric
summarize it.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">xclip </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-o</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-sel</span><span class="token plain"> clipboard </span><span class="token operator">|</span><span class="token plain"> summarize</span><br></span></code></pre></div></div>
<p><strong>ONE SENTENCE SUMMARY:</strong><br>
<!-- -->Aider is an AI-powered pair programming tool that enhances coding
productivity by integrating with local git repositories and various
LLMs.</p>
<p><strong>MAIN POINTS:</strong><br>
<!-- -->1. Aider facilitates pair programming with AI, specifically designed
for local git repositories.<br>
<!-- -->2. Compatible with leading LLMs like GPT-4o and Claude 3 Opus, offering
versatile AI assistance.<br>
<!-- -->3. Installation and setup are straightforward, requiring only a few
commands to start.<br>
<!-- -->4. Supports editing multiple files simultaneously for comprehensive
code modifications.<br>
<!-- -->5. Automatically commits changes to git with appropriate commit
messages, streamlining version control.<br>
<!-- -->6. Compatible with a wide range of programming languages, including
Python, JavaScript, and more.<br>
<!-- -->7. Utilizes a complete map of your git repository for better context
understanding in larger projects.<br>
<!-- -->8. Allows for voice commands and adding images or URLs in chat for
enhanced interaction.<br>
<!-- -->9. Achieved the top score on SWE Bench, indicating superior performance
in solving real GitHub issues.<br>
<!-- -->10. Offers extensive documentation, tutorials, and a supportive Discord
community for users.</p>
<p><strong>TAKEAWAYS:</strong><br>
<!-- -->1. Aider significantly boosts coding efficiency by automating tasks and
providing intelligent suggestions.<br>
<!-- -->2. Its compatibility with major LLMs ensures a flexible and powerful
coding assistant experience.<br>
<!-- -->3. The tool’s ability to understand and navigate large codebases makes
it suitable for complex projects.<br>
<!-- -->4. Community feedback highlights Aider’s impact on productivity and its
user-friendly design.<br>
<!-- -->5. Aider’s recognition in benchmarks underscores its effectiveness in
addressing real-world coding challenges.</p>
<h1>Bonus for NeoVim Nerds</h1>
<p>If you happen to use the best editor known to mankind… <code>neovim</code> btw, you
are in for a treat. Neovim plugins ecosystem, due to adopting <code>lua</code> as
plugins programming language, is very strong and versatile. Here are two
plugins that I use almost daily when coding, creating documentation or
chatting with <code>LLMs</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gennvim">Gen.nvim<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#gennvim" class="hash-link" aria-label="Direct link to Gen.nvim" title="Direct link to Gen.nvim" translate="no">​</a></h2>
<p>Let’s us use locall ollama models as a neovim copilot.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="gpnvim">Gp.nvim<a href="https://frosty-babbage-3125a3.netlify.app/blog/5-must-have-ai-tools#gpnvim" class="hash-link" aria-label="Direct link to Gp.nvim" title="Direct link to Gp.nvim" translate="no">​</a></h2>
<p>Provides rich chat experience and copilot-like functionality in the
editor.</p>
<p>Ok, we went through a lot of tools, let’s summarize:</p>
<table><thead><tr><th>Tool</th><th>Category</th><th>Description</th><th>URL</th></tr></thead><tbody><tr><td>Ollama</td><td>Local AI Models</td><td>Run AI models locally and interact with them through terminal.</td><td><a href="https://github.com/ollama/ollama" target="_blank" rel="noopener noreferrer" class="">Ollama GitHub</a></td></tr><tr><td>Fabric</td><td>AI Framework</td><td>Modular framework for solving problems using AI prompts.</td><td><a href="https://github.com/danielmiessler/fabric" target="_blank" rel="noopener noreferrer" class="">Fabric GitHub</a></td></tr><tr><td>Shell Automation</td><td>Command Line Automation</td><td>Tool to simplify various commands and automate tasks.</td><td><a href="https://github.com/Piotr1215/aicmdtools" target="_blank" rel="noopener noreferrer" class="">Shell Automation GitHub</a></td></tr><tr><td>AIChat</td><td>Terminal Chat</td><td>Integrates multiple AI platforms for chat via terminal.</td><td><a href="https://github.com/sigoden/aichat" target="_blank" rel="noopener noreferrer" class="">AIChat GitHub</a></td></tr><tr><td>Yai</td><td>Terminal Assistant</td><td>AI-powered assistant for terminal commands and tasks.</td><td><a href="https://github.com/ekkinox/yai" target="_blank" rel="noopener noreferrer" class="">Yai GitHub</a></td></tr><tr><td>Aider</td><td>AI Pair Programming</td><td>AI tool for pair programming with local git integration.</td><td><a href="https://github.com/paul-gauthier/aider" target="_blank" rel="noopener noreferrer" class="">Aider GitHub</a></td></tr><tr><td>gen.nvim</td><td>Neovim Plugin</td><td>Generate text using LLMs with customizable prompts in Neovim.</td><td><a href="https://github.com/David-Kunz/gen.nvim" target="_blank" rel="noopener noreferrer" class="">gen.nvim GitHub</a></td></tr><tr><td>gp.nvim</td><td>Neovim Plugin</td><td>ChatGPT sessions and copilot functionality in Neovim.</td><td><a href="https://github.com/Robitx/gp.nvim" target="_blank" rel="noopener noreferrer" class="">gp.nvim GitHub</a></td></tr></tbody></table>
<h1>Closing Thoughts</h1>
<p>Most of the tools mentioned can work with proprietary models such as
OpenAI, Claude but also with open source models like the ones provided
by <code>ollama</code>.</p>
<p>Integrating AI with command line tools not only boosts productivity but
also transforms how we interact with technology. The tools mentioned
here, from Ollama to Fabric, offer powerful capabilities right at your
fingertips, enhancing automation, insight, and efficiency.</p>
<p>Ready to supercharge your terminal? Let me know which tool is your
favourite, did I miss some that you use and find valuable?</p>
<p>Thanks for taking the time to read this post. I hope you found it
interesting and informative.</p>
<p>🔗 <strong>Connect with me on</strong>
<a href="https://www.linkedin.com/in/piotr-zaniewski/" target="_blank" rel="noopener noreferrer" class=""><strong>LinkedIn</strong></a></p>
<p>🌐 <strong>Visit my</strong> <a href="https://cloudrumble.net/" target="_blank" rel="noopener noreferrer" class=""><strong>Website</strong></a></p>
<p>📺 <strong>Subscribe to my</strong> <a href="https://www.youtube.com/@cloud-native-corner" target="_blank" rel="noopener noreferrer" class=""><strong>YouTube
Channel</strong></a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[kubernetes-development-mirrord]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord</guid>
            <pubDate>Wed, 19 Jun 2024 12:51:06 GMT</pubDate>
            <description><![CDATA[title: "Why Fast Feedback Loops Matter When Working with Kubernetes"]]></description>
            <content:encoded><![CDATA[<p>title: "Why Fast Feedback Loops Matter When Working with Kubernetes"
date: 2024-06-19
tags: ['Kubernetes', 'mirrord']
<img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/980839_image0-e7d5615ed8d17e9e260d124b04c79e51.jpg" width="1000" height="563" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="debugging-and-testing-made-easy-with-mirrord">Debugging and Testing Made Easy with <code>mirrord</code><a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#debugging-and-testing-made-easy-with-mirrord" class="hash-link" aria-label="Direct link to debugging-and-testing-made-easy-with-mirrord" title="Direct link to debugging-and-testing-made-easy-with-mirrord" translate="no">​</a></h2>
<h1>Introduction</h1>
<p>In software development, fast, iterative feedback loops — known as dev
loops — are essential for rapid prototyping and innovation. However, the
complexity and disparity between local and remote execution environments
can make this process challenging. Instead of staying in a flow state
and quickly moving from idea to prototype, developers often have to deal
with adjacent issues: building, deploying, and managing CI/CD pipelines.</p>
<p>Overlaying Kubernetes on top of an already complex process can turn
development into a slow and painful experience. This complexity is why
so many attempts have been made to solve this problem. Today, we will
explore one of the most promising solutions.</p>
<blockquote>
<p>The key to fast development cycles is reducing feedback time. The
quicker you can test and debug, the faster you can iterate and
improve.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="who-is-this-content-for">Who Is This Content For?<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#who-is-this-content-for" class="hash-link" aria-label="Direct link to Who Is This Content For?" title="Direct link to Who Is This Content For?" translate="no">​</a></h2>
<p>This blog is for developers, DevOps engineers, and tech leads who work
with Kubernetes and are tired of slow development cycles. If you’re
looking for ways to improve your productivity and streamline your
workflow, this is for you.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pain-of-slow-dev-loops">The Pain of Slow Dev Loops<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#the-pain-of-slow-dev-loops" class="hash-link" aria-label="Direct link to The Pain of Slow Dev Loops" title="Direct link to The Pain of Slow Dev Loops" translate="no">​</a></h2>
<p>Working with Kubernetes typically involves:</p>
<ul>
<li class=""><strong>Creating Containers</strong>: This step can be
time-consuming, especially when dealing with complex
applications.</li>
<li class=""><strong>Pushing Images</strong>: Uploading images to a registry
can take a significant amount of time, depending on the size of the
image and the network speed.</li>
<li class=""><strong>Deploying to the Cluster</strong>: Waiting for the
Kubernetes cluster to pull the image and start the containers adds
to the delay.</li>
<li class=""><strong>Waiting for Feedback</strong>: Once the application is
running, developers need to wait for logs and test results, which
can take several minutes or more.</li>
</ul>
<p>Each of these steps contributes to a slow development cycle, making it
difficult to quickly test and debug changes. This can be particularly
frustrating when trying to resolve critical issues or implement new
features under tight deadlines.</p>
<p>By the end of this blog, you’ll learn strategies to speed up your
development loop, reduce waiting times, and make debugging and testing
more efficient.</p>
<h1>Fast Feedback = Rapid Prototyping</h1>
<p>What if we could integrate development loops so that the inner loop
(code, test, build/run) seamlessly extends into the outer loop (build,
test, scan, deploy, release)?</p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/980839_image2-9e6d4a81b680947fdf5d140e4510e98c.jpg" width="700" height="700" class="img_ev3q"></p>
<p>This would result in much faster feedback and iteration, leading to
increased innovation. However, the development environment on my laptop
or in a remote containerized service like Gitpod differs from the
execution environment where the deployment is running.</p>
<p>What are the options?</p>
<ul>
<li class=""><strong>Recreate the Kubernetes environment locally?</strong><br>
<!-- -->Viable for some workloads.</li>
<li class=""><strong>What about recreating a cloud environment
locally?</strong><br>
<!-- -->Possible with <a href="https://www.localstack.cloud/" target="_blank" rel="noopener noreferrer" class="">LocalStack</a> with AWS
or <a href="https://testcontainers.com/" target="_blank" rel="noopener noreferrer" class="">TestContainers</a>, but only to some
extent.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-lowest-common-denominator">The lowest common denominator<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#the-lowest-common-denominator" class="hash-link" aria-label="Direct link to The lowest common denominator" title="Direct link to The lowest common denominator" translate="no">​</a></h2>
<p>An interesting question to ask would be: <em>is there something that both
environments share?</em> They do share the same container, but that’s not
what I develop; it’s just a packaging mechanism. Digging deeper… how
about a <strong>process 💡?</strong></p>
<p>A process executing code runs on my machine the same way a process
executing code runs in a containerized environment in the cloud.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="execution-context-matters">Execution context matters<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#execution-context-matters" class="hash-link" aria-label="Direct link to Execution context matters" title="Direct link to Execution context matters" translate="no">​</a></h2>
<p>When executing code locally, everything needs to be set up on the local
machine, including <strong>environment variables, file access, and
networking</strong>. This setup ensures that the code can run as expected
within the local environment. However, the context changes significantly
when moving to a remote environment, such as a public or private cloud.</p>
<p>In a remote environment, which often includes a Kubernetes cluster
running in the cloud, the infrastructure is more complex. Environment
variables might be managed through cloud services or orchestration
tools, file access could involve distributed storage systems, and
networking might include virtual networks, load balancers, and security
groups. Additionally, the context may include various integrated
services such as databases, message queues, and other networking
components.</p>
<p><img decoding="async" loading="lazy" alt="Photo by Shubham
Dhage
on
Unsplash" src="https://frosty-babbage-3125a3.netlify.app/assets/images/980839_image3-1c412809bfb90a13bbe20ee03b47786a.jpg" width="700" height="394" class="img_ev3q"></p>
<p>A piece of code that works perfectly on a local machine might fail in
the cloud due to missing environment variables, incorrect network
configurations, or issues accessing remote databases and queues. This
discrepancy forces developers to replicate the remote environment
locally or debug issues that only appear in the cloud, slowing down the
development cycle.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="testing-locally-with-remote-environment">Testing Locally with Remote Environment<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#testing-locally-with-remote-environment" class="hash-link" aria-label="Direct link to Testing Locally with Remote Environment" title="Direct link to Testing Locally with Remote Environment" translate="no">​</a></h2>
<p>So far we have established that in order to combine the two development
loops and make prototyping faster, we need to run our local process in
the context of its remote environment.</p>
<p>This is the recipe for success; <strong>run my local process in the context of
a remote environment.</strong></p>
<p><img decoding="async" loading="lazy" src="data:image/jpeg;base64,iVBORw0KGgoAAAANSUhEUgAAAGsAAACkCAYAAACQAdBtAAAACXBIWXMAAAsTAAALEwEAmpwYAAAYoElEQVR4nO1da3RTVdp+qn7fn9HmuFyMM4xt4tBRFNp0KBcLnaZQBEeWDW0ZRdZnaltBGCFpC4gj0kuKsHSmV1C5TC9pZ0ZASJs6gn60JWW4WKGQFpSIBVPp1FmLcTgJZf44ku9HVmrbnH2Sc84+ufj1Was/mpOz9z55zn73u9/9XqK+/fY7NyYQEbgj1AOYQOCYICuCMEFWBGGCrAjCBFkRhAmyIggTZEUQJsiKIEyQFUGYICuCcFeoB0ATLMuira0NbW2tYFkWAKBUqqDVLkVGRkaIRycdUT8U22BTkwnr1xeNkDQeGo0GFRWVUKsTgzwyevhBkJWfn4emJpPf7zEMg/b2joglLOLJslgsWLYsK+DvMwyDL764AoZhZByVPIhoBYNlWbzwQp7ge4zGMplGJC8imqyuri7iGsWH5uYmGUYjPyKarN5em6j7WJZFV1cX5dHIj4gjq7fXNjKbaPzgvb02DAw4JLcTDEQMWeXlRkyadB9mzkzCpEn3IS5uCgYGBkS3t2NHzUh7cXFTEBc3BRaLheKI6SMitMFAVXMaqKurh06XE5S+hCLsyWpqMiE/X5jGJwXhrNqHvRi0WFqD2h/Lsujt7Q1qn4Ei7MliWWfQ++zqsga9z0AgG1nl5UYsW5YlWr32QqVSUhpR4JBqjmJZFuXlRvrbg2+//c5N+6+urt4NwA3AzTCM++zZHlHtFBeXjLQTzD+lUiV6zNevf+NmGGakrf7+K9R+V1nIysjIGPPwanWi+/r1bwK+v7//iluj0YSEqNF/xcUlgp99/LjXrdOHN1lS3ta6uvoxb2ao/wJ90a5f/4bzBVMqVeFLVnt7J/HBGYZxFxeXEB9+tPgMpz9/ovzgQTPvC0ZLFFInq6KiMqCHH0+aXm8Q9UNGRyvcS5Zo3Vu3VroPHexwf3rxG/fgte/G/J0+dcXdYu5wb3q5xJ2SIk68chHW3t4ZkLhub++k8ttS3xQbjWUoLzcG9F2GYZCRoYXTyQo29axYkYPsbB3mzE4TMUrA3GLCgQMmnDgRuMbGMAwqKqrgdLIwmUwBa7q0rCIh9cFgWVawGemVTcXQ6QyIjpZmYcjKzEFWZg6GhhwoMxbhgw/8vywsyyI/P1dwXw6HQ8QIfRH2m2IvlizR4pPuK1i7tkQyUaMxebIKu3eZ0WLuQGysklq7coA6WbRtatHRCphMh7B7lxmTJ6uotj0as2al4dTJq3hlUzH1tjWaNCrtUCeLpjNKfLwa7UfPIX3BUmpt+sPatSVobDiE6GgFtTYZhk5b1BUMlmUxadJ9kttZsUKHza9WQaEIjfV7cNCBlauycOGCdKPut99+R2FEMonB1FSNpDZWrNDhzTcaqBA1NOQgXrPbbfjkjJXz2gMPqLB/Xyfi49WS+qfpXCqLgqHVakXf6yWKBto7WlFmLOK8ZjY3oqGhBlMfJovt6GhGMmFaLT0RLsvhI8uyiIv7OZxOYccbNInatr0Ibjew+dXKMZ87nSxe37YesTGxWLu2JKC2XC4WzyxfIFgkKhQK9PdfpaZ0yTKzGIaBXm8QdE98vJoKUS4Xi+XPLsCcOak+RF26ZMOLq7Pw9NPPBUwU4JlhlRX1gpUOvd5AVTuW9Vg/PX0Bjh/3byGIjVXiwyPnJO+fPjljRWWlEaUllZg6dax4a+9oxXvvNaHiD/W4+25x/XR/YkV2dnpA301IUKOn55yofkiQlayBAQeSkmb4FYftR3t8flyhaG6uxYWL5/Ha5iof0l/fth6A22emiYGpqQabN3Ovg14oFAr09JyDUqmS3N9oyGrBUCpVfm1imzYVSyLK5WKxek023G433nyjYQxRg4MOLF++AI899isqRAFAjs6AlBR+bbe4uIQ6UYDMM4tlWfziF1OILs7Tp6vx4RHxosJut6GwKA9VlfU+hB9tb0VdXS0qK+sx+acq0X1wYXDQgUWLZ8Dl4pYYGo0G7e2dVPsEZJ5ZtbU1RKKioxWorzOLbrulxYSqaiP27+v0IWrnW2U4etSC/fs6qRMFePZgW7dWEa93dXXJ4p4tG1ksy2LHjlri9TVrDKJsfS4Xi02v5OHvf3dg9y7zGLHncrFY9WImfvYzFd58owFu2WSGx2rPJw7Ly+lHqshGFt+sio1VIkcnTLUHPNaIVS9mYdGiDB/Vu7vbilWrslBUWILMpdLPjgJBYSHZ6CvH7JKNrKYmcljNxg3CjzmGhhwoWp+Hyop6H8Nuc3MtzGYT9uwxS9YqhWDO7DSsWKEjXq+trabanywKBp/Lc0qKBvvepbP4ulwsXt6Uj7nJqdCJmKk0MDjowGPJU4jX+/uvUNMMZZlZfKe/zzxNR0TZ7TasWpWFAsOWkBEFeJQNvtllMtELqKA+swYGHIiL437TYmOVOHXyquQ+WlpM+PAjC37/Zj3VU2OxGBpyYPYc7mdWKlXo779CpR/qM6umpoZ4beOGwO1xXODTBEOJyZPJs2tgwEEt7os6WW1tbZyfx8YqkZkpXgTa7TasejELy7J1goywwULu82RR3NZGJxKGqncTX8jnr38t/ozLbG7ER//bhj27w2c2jccjjyQiPl7NeYxCeoGFgurM4ltM8/PEKQHbthdh6OuBsBJ7JKxayf2MLCvcL5ILVMkipSxISdEItlaMPpda+1L4iT0upKeTpQcNUUiNrNFR9OMhRl0fGnJwboClor2jFU3NdDerXkRHM0RFg4YopEYW3zTne+NImDo1kaqfoNPp2UB3dx+H7rkCau2Ox6JF3M9KI/cGNbJIA1myRBvytcZu9xzn/2bZc9TOtUhYmE6WBFLDX6mQxbIs8fh+7lxpbmlS4T1K2bPbjFmz0oLS55Il3LNLqiikQhbf9H58oXiVXQpCuYF+YjH3M/Ot64GAClmk0Jf4eLWs/ul8+PxzG7KyQrOB5lujpaRtkHVmzZsXOhE4a1aa6NgtqYiOZoiOoVLWLSoWjL4+7rfl0UcC92T9r/8GLlywQaFgMPmnKllPeYXg0iUbDpmbcfGiZ2xz56YhfcFTeOABFe99JGuGlFQTkmcWnxx+7LE0v/dfv+7Ab1/Kwv3334mFC5Mwa9YUzJ03Be+8Y8TwsHj5LhVDQw6s+W02Hl+UhMOHWxAV5YbTeQObNxdg8RNJOHiokff+ZMKzHz9+XPSYJM8skgyOjVX6Xa/++Y0D8xckgWVZTJumxuJFWrhcLD7u7sLr28rw1ts1MDUeQlJSmtRhCsLly+eRmbUQsbFKtJg7xmiRw8MsNmzMRUFBPmIeUGHOHO6xzZnDvQSwLIuBAYeoA0nJZJFCMOPj/R+vv/pqIW7fduOjD3swbdrY758+bcX6DXnQLk3Hjtp6SRZ7IbDbbVj2m4VQKlUoK6vCww8ljLl+990Mdu9qQVNzNWJiVMR2Jk9WITpawemu5nAMiCJLshgkKRfTp/tfrwz6Erx3oHMMUXfeCbhcA4i6w4WlS5eCYRis0+fh8uXzUofqF8PDLPJfyAbLsujrsyEzcz4enXYfVq/JxuCgY+R7bjfw3P8U+JUcCQncL6xYJUMyWSTX6EcCUC6mTUscQ9RddwHv7DJi7rwZWLYsEyZT48h6mJu3DC6XvGtY+dZCsOwN6PV66HTf2/j++tdWLH4iSXD/pBdW7F6LioLBhQd+phLUTlQUsPX1QhiNZTAYDHC73WBZFjdu3EBlZRVY9gaa/ySPARYAenqs+POfm9DS0oqamhqfbGxOJ4uNL+cLapMkJsXutSSRxZdbVqhLWP8VG3burEVVVRVKS0tHPmcYBoWFBTAYDNi+vRxf/4Pcp1hERQEVleXIyNBi/vw0ANxv/wcftOLTzwJXvUnSRWy6WElkORzcnYqJFNy7twZKpRIFBdwW8a4uj8prNK4X3LY/tFoa0dt7HjU138/ctDRube7MJ4Fbzkmu22ITKEsiiyR7hcYC33kn8Le/HSeGdJ4/f35kUX7//Vb09FgFtc+H4WEWr766HgZDAVQq1cjnZWVluHr1S5/46IufBj6z+DbON2/eFDpUaWSR1iuhEYJ33Ol529LS0jivW63WMf/rDfmIihLUBRG1O8rAMAqUlZWO+bykpBS5ubn46qux0mPwmjARRvotLl/+XFA7gExOnoHssUbjwgUP6aSQzvEzeGDAgbfeEu74/+m4WdHTY8Xbb9eioaHR57sMo0BXl9V3HynwJSGp72IgixgUCn9iMzc310ccbdtuxJmz1pH/L18+D2sX2c/h6384sPiJJHR3e+4ZGnJAl5MNvd4wolSMRlrafM523JSMljdvDgu+R6IY5FZBA9ljjYZ3ISZZQ1QqFaxWKzo7x/rIP/989kieiw8/asNLL5HFo9nciOhoBaZOTYTLxeKFldlQKpU+4s+LX/4yEUqlby6nlJS0AJ7oe5D2Wl98cVlQO4BMYlDoQZ/b7bEl2mzca+CxY8dwxx1RWLBgwfg78cLKbNy6xWJZdg6cThZmM7c73OHD7+PJJ7W4dYvF8mfT0ddng8XSyhtNn5PzvM9nU6cKexFpZsgJm6xo06eriaarxMRfcn6uVieir8+GRYuT8O9/s4iNVWLw7w6f7911l0cZGhz8Co8vSkJfn+elGK39caGwsAAKxVgFYfasNNjtNtmtKVwIOVmnP7bgylUbnlisRW+vjVMU3nsvwymSEhM9bznL3kDa/CS4XE6cPMlNuEKhwIkTVsTHq7F9e0VAY2MYBi0trSOEPflkBjo7LVj4eBIO+TkikQMhJ6uhoRFabfqI1lRSUsr5vZwcX6s7w9wLAPjwyDnMnasBy7JITvbdzP7nP4D12Dl0dPRg37udmD49cA1t/vy0kTHFxqrw2pYizJuXitxc+dzZSAg5WVteq8Lt27dhKMhHfLwaTU0mztmVm0vOoBkTo8KB/Z0YvPYdDHpun4tJk1R4+CHhajTLsqit9UTG7NpVi9hYJfbuaRHcDg2EXHWPiVHhvQPH8OWXV0eOwQsKCn2+p1KpUFz8PREesegWlRdw8k89InX8ZpsLBQWFIy/PihU67N/XScVTKugWDFp5iaZNS8R7B46NqLkWSyuqqnwt7GVlpdDrDVAoFKiurobF0ibKKecnP3kQCQlqzs3waDQ0NMJk8nxn+7YKn6QoUnDPPfcIvifkYtCLadMSsX9fJ555xnOOVFRUiJYWX3FTU1MNlmWhVKrQ22sLyM9jPNxu4LdrDGhqMuH8ee7tQkFBIfLycpGSokH70R48J6PLdaAIG7IAz56k4g8NeO9AB2JjlMjKykJpaamPuP3ySwdyc3MRExMrOpToqady8KtfabBgwXxUV9fAZrPBZrOhuroGDz74IGpqqrFmjQH73vVNihIqhGW59uTkNBw5cg4bNuahrKwMNTU10GqXQqVSwuEYgMXSCvdtN/74R/EZatxuYP++Try+rRCFhWNnTXy82sdRJhwQlmQBnlm2d48Zp09bUV1jHFk7ACA5WYPKinpeh5VAcPs28LtXqpCXp4f9kke5uf9+VdjMpPGQhazhm/R298nJaUhOTgPg8Xh69NFE6kmO7//xg7j/xw9SbdOLa9cc1NqStGZpNNya2KefyVOGLzk5LWTZqMVi/HmYFzNmzBDcVlgpGBPghyxkXbwYngUuQwEhhWn8QRJZpOoITmfofNQjBQ899LDge2SxYFwT6KfwQwVfAYCgWzDUapJfnENKs7Ih2DOe62wN8GSmFgPZbIOkshGhgDdSv4vHR0MOdHdzr1dibaqSFQzSW0JzryUFoxPvZ2Q8H9S+SXsskkTyB8lkkYo+y7XXEoJGUy2qa4z4414zZgY5xgsg77H8uROQIJkskkYYSvXd6WTx4upsDA/fwO5dZtEVEqSC9BuIrTEmmSxS1TWa+wsh+PQzj9hbuXIdZ86nwUFHUJxd7HZy+C7J8uMPsolBp5PlVV3lgkqpIoq97m4rNmzMwx1BsNt0EwIYuBx/AoVkQ65SqYJCoeAMquvu7kJmpkpqF4Lwox9xi7ydO8vx1bWr2L+vMyiZAD4jBDCIVS4ASuYm0rQ+/bGVRvOS4HKx2LQpFzExMbIn5h+NEye5o/KlFOukRBb3AE4SBhwseDNU5+YaoNU+H7R+h4YcRMOAlAKmVMgiDWBgwAG7XXySDikYSbBFITG/J3984MWkj7aT0/mJVS4AimJwvJuxF6SFVi54E2x51Xap3kh2uw0bX85DXm7gvh6nTnE/s9TCpdT0ItIbs38/ucwFbVy69H2GahreSM3NtaiqNuL3b/qWfCLB5WKJpd+lFC4FKJJFCjHt67MFJa1Pe0friLVCqqPL6AJqQmfn2VExY+MhRQQCQZhZAHCIEIZDC9u2F6G7+zgVa4XUshn7D3BLEqVSKbk6OjWylEoV0agrlyh0uVi8uDqLs3KqGEhVSoaHySJQ6qwCKB/rc0V6AB5RKIdWuPHlPBQWFEvOUE0r66e5pZF4LSNDehZtqoVj+IrG0CwYTROXLtlQZizC+qJiyWvdvJQpnPsrhUKBf/7zX5LaBijPLD5R+MEHbSGJFuSD2dyIMmMRFaXkkzNW4kbYX0XZQEHdpEkShU4nC1MTuQJQsLFtexEu2fuw791OKkcoVVXlxGuk30QoqNfPYlkWkybdx3lNqVTh5Ak6taTEYnDQgQ0b87BypZ5aNYav/+HArFmk+llK9PdLrxkGyDCzGIYZk/5tNAYGHGhpkVeN58PR9lZs2JiHykq6ZTOqq8gJVPR6elXzZKn52NXVhYULx6dB8CBUs2vnW2X46isHZzl3KeCrSqdQKNDff5Va0KEsx3AajYZoBxsYcGDHTvo1fEnw7sW8tYtpJ+Mv30rO0qbVaqkRBchYrp2voqpCweD0qSuyVzHo7raiqtqI0pJKWcJ4zpy1IjMznXidZiVVQMbABJ0uh3iE7XSy2Pq6b5A3TezcWY76hlrs2c1vjRgacmD5swtEuSCUlJBnlU6no0oUIHMUyejo+vH4y1+aYP+cvlXDa4SNjo72a41o72gdKVQttAxHU3P1SKYaLvA9u1jIJga9iIv7OTHNqFKpwpHDPdTEod1uQ2lZEcrKKnlzXjidLN562wi3G6Jsip504ulE7yWdToe6OvrWGtnJ4tMMAWD1aj1e21wluR9T0w4cOdyCPXv4Z5PXvCR2nxUVBfz6ySTeWUV7rfJCdqcsPs0Q8GRtOcNzBhQIXt6Uj5uuf2Gfn4QiZnMjqmuMksoT7thZykvUunV6WYgCgjCzAE9Gspkzk4jXFQoGhw52iNbYBgcdfgu5AMDZHqskN+qWFhPW6bk1XID+vmo8ghKmqlYnYsuWYuJ1p5NF0fp80YbeQIgCIIko++c2bCku4v1OXV2DbEQBQZpZgMdmOHPmDN6c5gkJiThyuCdsyjF5cesWi9lzpvDGd6WmatDR0Um8TgNBCwBnGMavhtTXZ8PqNVlhdZRy6xaL3zydzkuUQqFAfX297GMJarS+RqPhFYcA8P77Fix/Nj0sCLPbbZg9ZwqvQgEAlZVVsikVoxE0MTga6ekLiNVXvUhISMS+dztCVjbX/rkN2dn8MwqQb0/FhZCQxbIs4uJ+TqwQ5IVCwaCh4RBmBzmHkqWtES+95L9ITEKCGh0dnbIqFaMRkqQlDMOgo6OT6MXrhdPJIisrHc1/Cs4Js8vF4pVX8gIiSqFQwGw2B40oIIQZZtTqRFRWBma5+N3vivDkkiRZg8rb3jchee4U/OnP/g9HFQoFOjo6g7JOjUZIxOBo8B2lcOGpp5bCYNiCqQ/TOfI4ZrVg795aHD9uDfiegwfNkl2hxSDkZAHCCQM8CsiqVQZkLtUJ3pcND7NotZjwzju1gnN21NXVU/NWEoqwIAsQR5gXCQmJSElJRXJyGu6++17cc080pj2aiKgo4PTHnhixr4e+xKlTXfjbiS5RSVW8ok+qC7QUhA1ZAGCxWJCfn+tXSww2woEoIMxS2Gm1WnR0dIpOlyMHEhLU6O+/GnKigDAjC/BoiR0dncjIyAj1ULBunR49PeeCqp7zIazE4HiESiwqlUrU1TVQifygibCbWaOh1WpDonnp9YawIwoIc7IA8TV9pYBWxT3aCHuyQvGGi02EJTfCes0C+GO+5IDcR/NSEPYzS6lUoaJCeghqoKisrApLooAIIAvwLPgHD5rH7L9SU/m9pvwhNVUzxmM4NVWD9vbOkJmSAkHYi0E+GI1lKC83irq3oqKSajhOMBARM4sEKeIqHCwSQhHRZEnRFKWkkgsVIpostTpR1Lql0+nCVongQ0SvWYB/b9/xUCgU6Ok5F/RTXhqI6JkFeGZXXV1gPnuhOo6nhYgnC/AE7p0928N7tOL1mI1ExcKLiBeD49Hba0Nvby/6+/sBAHFxcZg3bx6mTIkL8cik4wdH1g8ZPwgx+P8FE2RFECbIiiBMkBVBmCArgjBBVgRhgqwIwgRZEYQJsiII/weYHjYymuAjJAAAAABJRU5ErkJggg==" width="107" height="164" class="img_ev3q"></p>
<p>Here is where <code>mirrord</code> can help!</p>
<h1>Mirrord</h1>
<p>Does exactly what we need:</p>
<blockquote>
<p>mirrord is an open-source tool that lets developers run <strong>local
processes</strong> in the <strong>context of their cloud environment</strong>. It makes it
incredibly easy to test your code on a cloud environment (e.g.
staging) without actually going through the hassle of Dockerization,
CI, or deployment, and without disrupting the environment by deploying
untested code</p>
</blockquote>
<p><a href="https://mirrord.dev/docs/overview/introduction/" target="_blank" rel="noopener noreferrer" class="">https://mirrord.dev/docs/overview/introduction/</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-app-in-local-context">Running App In Local Context<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#running-app-in-local-context" class="hash-link" aria-label="Direct link to Running App In Local Context" title="Direct link to Running App In Local Context" translate="no">​</a></h2>
<p>A simple <code>nodejs</code> app connects to <a href="https://learn.microsoft.com/en-us/azure/storage/common/storage-account-overview" target="_blank" rel="noopener noreferrer" class="">Azure Storage
Account</a>
and displays file content. As a developer, my task is to test my app.
Let’s run it locally by executing those 2 <code>justfile</code> recipes:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">start_server:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> nodemon server.js</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">browser: start_server </span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"> browser-sync start - proxy </span><span class="token string" style="color:rgb(255, 121, 198)">"localhost:3000"</span><span class="token plain"> - files </span><span class="token string" style="color:rgb(255, 121, 198)">"server.js"</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"public/**/*"</span><br></span></code></pre></div></div>
<blockquote>
<p>If you want to learn more about <code>justfile</code>:</p>
</blockquote>
<p><a href="https://itnext.io/master-command-orchestration-16f4a117ce05?source=post_page-----58b8d2cb8e8e--------------------------------" target="_blank" rel="noopener noreferrer" class=""></a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="master-command-orchestration">Master Command Orchestration<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#master-command-orchestration" class="hash-link" aria-label="Direct link to Master Command Orchestration" title="Direct link to Master Command Orchestration" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="transform-your-projects-with-just">Transform Your Projects with Just<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#transform-your-projects-with-just" class="hash-link" aria-label="Direct link to Transform Your Projects with Just" title="Direct link to Transform Your Projects with Just" translate="no">​</a></h3>
<p>itnext.io</p>
<p><img decoding="async" loading="lazy" alt="My app works, but there is connectivity
error" src="https://frosty-babbage-3125a3.netlify.app/assets/images/980839_image5-52fead41765ab46ebf0633c67e696227.jpg" width="700" height="151" class="img_ev3q"></p>
<p>Right, that’s not going to help us test a new version of my app. With
<code>mirrord</code>, we can connect my local process to a remote execution
environment and my app running in a pod.</p>
<p>This results in the <strong>traffic, environment variables, and file
operations</strong> being mirrored into my locally running process. Whew,
that’s a mouthful. Let’s run <code>mirrord</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token comment" style="color:rgb(98, 114, 164)"># Run mirrord on deployment, this resolves to a single pod</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">mirrord:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  @mirrord </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"> --target-namespace devops-team </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--target</span><span class="token plain"> deployment/foo-app-deployment </span><span class="token punctuation" style="color:rgb(248, 248, 242)">\</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">   nodemon server.js</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">✗ just mirrord</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">New mirrord version available: </span><span class="token number">3.106</span><span class="token plain">.0. To update, run: </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">`</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">"curl </span><span class="token variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-fsSL</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> https://raw.githubusercontent.com/metalbear-co/mirrord/main/scripts/install.sh </span><span class="token variable operator" style="color:rgb(189, 147, 249);font-style:italic">|</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> bash"</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">`</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">To disable version checks, </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">set</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">env</span><span class="token plain"> variable MIRRORD_CHECK_VERSION to </span><span class="token string" style="color:rgb(255, 121, 198)">'false'</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">When targeting multi-pod deployments, mirrord impersonates the first pod </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> the deployment.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Support </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> multi-pod impersonation requires the mirrord operator, </span><span class="token function" style="color:rgb(80, 250, 123)">which</span><span class="token plain"> is part of mirrord </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> Teams.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">You can get started with mirrord </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> Teams at this link: https://mirrord.dev/docs/overview/teams/</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">* Running binary </span><span class="token string" style="color:rgb(255, 121, 198)">"nodemon"</span><span class="token plain"> with arguments: </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token string" style="color:rgb(255, 121, 198)">"server.js"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain">.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">* mirrord will target: deployment/foo-app-deployment, no configuration </span><span class="token function" style="color:rgb(80, 250, 123)">file</span><span class="token plain"> was loaded</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">* operator: the operator will be used </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">if</span><span class="token plain"> possible</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">* env: all environment variables will be fetched</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">* fs: </span><span class="token function" style="color:rgb(80, 250, 123)">file</span><span class="token plain"> operations will default to </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">read</span><span class="token plain"> only from the remote</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">* incoming: incoming traffic will be mirrored</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">* outgoing: forwarding is enabled on TCP and UDP</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">* dns: DNS will be resolved remotely</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">⠖ mirrord </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">exec</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ✓ Update to </span><span class="token number">3.106</span><span class="token plain">.0 available</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ✓ ready to launch process</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      ✓ layer extracted</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      ✓ operator not found</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      ✓ agent pod created</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      ✓ pod is ready</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    ✓ config summary                                                    </span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">nodemon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> </span><span class="token number">3.1</span><span class="token plain">.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">nodemon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> to restart at any time, enter </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">`</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">rs</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">nodemon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> watching path</span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">s</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain">: *.*</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">nodemon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> watching extensions: js,mjs,cjs,json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">[</span><span class="token plain">nodemon</span><span class="token punctuation" style="color:rgb(248, 248, 242)">]</span><span class="token plain"> starting </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">`</span><span class="token variable function" style="color:rgb(80, 250, 123);font-style:italic">node</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic"> server.js</span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">`</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Listing all containers and their first blob:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Server running on port </span><span class="token number">3000</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Container: sample-blob</span><br></span></code></pre></div></div>
<p>Notice that now all environmental variables are fetched from the remote
execution environment (Kubernetes cluster).</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">* env: all environment variables will be fetched</span><br></span></code></pre></div></div>
<p>Now we should be able to navigate to localhost:3000 and see that the
connection was successful:</p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/980839_image6-9da1d14edd01c2880f78e9982b0b3f28.jpg" width="700" height="151" class="img_ev3q"></p>
<blockquote>
<p>What we have achieved is that calls from our local process are as if
they were made by the remote process and calls to the remote process
are mirrored to our local process.</p>
</blockquote>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-does-mirrord-work">How does <code>mirrord</code> work<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#how-does-mirrord-work" class="hash-link" aria-label="Direct link to how-does-mirrord-work" title="Direct link to how-does-mirrord-work" translate="no">​</a></h2>
<p>Simplifying, <code>mirrord</code> operates in an agentless mode, using <code>kubectl</code> to
connect to a cluster and create a temporary pod for mirroring. The key
components include the <strong>mirrordAgent</strong>, a Rust binary running as a
Kubernetes job that proxies local processes by sniffing network traffic
and accessing the file system of the target pod. The <strong>mirrordLayer</strong>, a
dynamic library, hooks into the local process to relay file system and
network operations to the <strong>mirrordAgent</strong>.</p>
<p><img decoding="async" loading="lazy" alt="source: author based on
https://mirrord.dev/docs/reference/architecture/" src="https://frosty-babbage-3125a3.netlify.app/assets/images/980839_image7-37b0f3f5937cc53301b435db2c0b2f18.jpg" width="1000" height="388" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="real-world-is-messier-than-that">Real world is messier than that<a href="https://frosty-babbage-3125a3.netlify.app/blog/kubernetes-development-mirrord#real-world-is-messier-than-that" class="hash-link" aria-label="Direct link to Real world is messier than that" title="Direct link to Real world is messier than that" translate="no">​</a></h2>
<p>Now we can iterate rapidly on a new version of my app and see how it
behaves in the actual remote execution environment. This is however a
simple example, there might be scenarios where we want to:</p>
<ul>
<li class="">redirect or
<a href="https://mirrord.dev/docs/using-mirrord/steal/" target="_blank" rel="noopener noreferrer" class=""><strong>steal</strong></a> all the
traffic instead of mirroring it</li>
<li class="">redirect only
<a href="https://mirrord.dev/docs/using-mirrord/steal/" target="_blank" rel="noopener noreferrer" class=""><strong>subset</strong></a> of calls
or <a href="https://mirrord.dev/docs/using-mirrord/steal/#filtering-out-healthchecks-using-a-negative-look-ahead" target="_blank" rel="noopener noreferrer" class=""><strong>filtering
out</strong></a>
Kubernetes health checks</li>
<li class="">redirect <a href="https://mirrord.dev/docs/using-mirrord/outgoing-filter/" target="_blank" rel="noopener noreferrer" class=""><strong>database
writes</strong></a>
to a local db to avoid duplicated remote writes</li>
<li class="">run a new app or a tool in the remote context using
<a href="https://mirrord.dev/docs/using-mirrord/targetless/" target="_blank" rel="noopener noreferrer" class=""><strong>targetless</strong></a>
mode</li>
</ul>
<p><code>Mirrord</code> supports all those scenarios and more are being developed.</p>
<h1>Closing Thoughts</h1>
<p>Developing containerized applications for Kubernetes is hard. There are
many options to help with the process, including tools like
<code>telepresence</code>, <code>skaffold</code>, <code>okteto</code>, <code>garden</code>, and many more.</p>
<p>This tells us two things: first, there is no standardized way of
supporting developers in their efforts; and second, the space is still
growing and maturing. <code>mirrord</code> has a unique place in this ecosystem.
The blend of very little to no friction and strong defaults makes it an
attractive solution for cloud-native development.</p>
<p>By seamlessly mirroring traffic, environment variables, and file
operations from the remote environment to the local process, it
eliminates the common pain points in Kubernetes development. This
approach not only speeds up the development cycle but also ensures that
your local environment is as close to production as possible, reducing
the risk of unexpected issues during deployment.</p>
<p>Thanks for taking the time to read this post. I hope you found it
interesting and informative.</p>
<p>🔗 <strong>Connect with me on</strong>
<a href="https://www.linkedin.com/in/piotr-zaniewski/" target="_blank" rel="noopener noreferrer" class=""><strong>LinkedIn</strong></a></p>
<p>🌐 <strong>Visit my</strong> <a href="https://cloudrumble.net/" target="_blank" rel="noopener noreferrer" class=""><strong>Website</strong></a></p>
<p>📺 <strong>Subscribe to my</strong> <a href="https://www.youtube.com/@cloud-native-corner" target="_blank" rel="noopener noreferrer" class=""><strong>YouTube
Channel</strong></a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[building-pipelines-dagger]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger</guid>
            <pubDate>Fri, 14 Jun 2024 12:40:04 GMT</pubDate>
            <description><![CDATA[title: "Why is Building Pipelines Different from Software Development?"]]></description>
            <content:encoded><![CDATA[<p>title: "Why is Building Pipelines Different from Software Development?"
date: 2024-06-14
tags: ['cicd', 'pipelines']
<img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image1-444153810813f0fb663eb3dd6df656c5.jpg" width="1000" height="563" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="it-doesnt-have-to-be-simplify-your-cicd-workflow-with-dagger">It Doesn’t Have to Be! Simplify Your CI/CD Workflow with Dagger<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#it-doesnt-have-to-be-simplify-your-cicd-workflow-with-dagger" class="hash-link" aria-label="Direct link to It Doesn’t Have to Be! Simplify Your CI/CD Workflow with Dagger" title="Direct link to It Doesn’t Have to Be! Simplify Your CI/CD Workflow with Dagger" translate="no">​</a></h2>
<h1>Introduction</h1>
<p>CI/CD pipelines are essential for automating the process of software
integration and deployment, ensuring that code changes are automatically
tested, integrated, and deployed to production with minimal manual
intervention or ideally in a fully automated way.</p>
<p>However, building and managing pipelines is not easy. In this blog, we
will look into addressing some of the most common pinpoints of pipelines
development and see how to improve.</p>
<p>This content will be valuable for Developers, Architects, DevOps
Specialists, or anyone curious about how to improve building and
maintaining CI/CD pipelines.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="challenges-of-building-and-running-a-cicd-pipeline">Challenges of building and running a CI/CD pipeline<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#challenges-of-building-and-running-a-cicd-pipeline" class="hash-link" aria-label="Direct link to Challenges of building and running a CI/CD pipeline" title="Direct link to Challenges of building and running a CI/CD pipeline" translate="no">​</a></h2>
<p>The main challenge comes from the fact that CI/CD pipelines development
and lifecycle management is treated differently from software
development practices.</p>
<p>Nowadays, pipelines are mostly written in YAML. Large configuration
files instruct pipeline runners hosted on services like GitLab or GitHub
how to interpret a pipeline workflow file and what actions should
happen.</p>
<blockquote>
<p>👉 <a href="https://medium.com/itnext/azure-explained-deep-enough-azure-devops-210629b5480e" target="_blank" rel="noopener noreferrer" class="">Read more about YAML file structure using Azure DevOps as an
example.</a></p>
</blockquote>
<p>Individual actions are wrapped with YAML tasks or steps which are in
turn often bash scripts executed in the runner’s environment. Here is an
example build job with multiple steps. Actions are used to call
specialized steps and by default, the runner will execute commands
provided in the <code>run</code> block.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">  build:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    runs-on: ubuntu-22.04</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    steps:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      - name: Checkout</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        uses: actions/checkout@v4</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      - name: Setup Python</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        uses: actions/setup-python@v5</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        with:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          python-version: </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${{ env.PYTHON_VERSION }</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      - name: Setup Hatch</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        run: pipx </span><span class="token function" style="color:rgb(80, 250, 123)">install</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">hatch</span><span class="token operator">==</span><span class="token number">1.7</span><span class="token plain">.0</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      - name: Set Default PyPI Project Version</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        if: env.PYPI_VERSION </span><span class="token operator">==</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">''</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        run: </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">echo</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"PYPI_VERSION=v0.0.0+</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">date</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> -d@</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">$(</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">git show </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-s</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--format</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">=</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">%ct</span><span class="token string variable punctuation" style="color:rgb(248, 248, 242);font-style:italic">)</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> +%Y%m%d%H%M%S</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">-</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">$(</span><span class="token string variable function" style="color:rgb(80, 250, 123);font-style:italic">git</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> rev-parse </span><span class="token string variable parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--short</span><span class="token string variable operator" style="color:rgb(189, 147, 249);font-style:italic">=</span><span class="token string variable number" style="color:rgb(189, 147, 249);font-style:italic">12</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic"> HEAD</span><span class="token string variable" style="color:rgb(189, 147, 249);font-style:italic">)</span><span class="token string" style="color:rgb(255, 121, 198)">"</span><span class="token plain"> </span><span class="token operator">&gt;&gt;</span><span class="token plain"> </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">$GITHUB_ENV</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      - name: Set PyPI Project Version</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        run: hatch version </span><span class="token variable" style="color:rgb(189, 147, 249);font-style:italic">${{ env.PYPI_VERSION }</span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      - name: Build Sdist and Wheel</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        run: hatch build</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">      - name: Upload Sdist and Wheel to GitHub</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        uses: actions/upload-artifact@v4</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        with:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          name: dist</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          path: </span><span class="token string" style="color:rgb(255, 121, 198)">"dist/*"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          if-no-files-found: error</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">          retention-days: </span><span class="token number">1</span><br></span></code></pre></div></div>
<p>The main issue is that the commands can be executed locally, however
there is no guarantee that the same commands will execute in the same
way in the runner’s environment.</p>
<blockquote>
<p>In other words, we cannot guarantee fully <strong>reproducibility</strong> of the
pipeline.</p>
</blockquote>
<p>Since reproducibility does not work, testing happens in the runner’s
environment. The only way to debug the workflow is to add print
statements or log to a file and dig in the runner’s log file to see what
went wrong (spoiler alert, most of the time it’s missing comma or
something equally trivial).</p>
<p>This typically results in commit history like this where pipeline
doesn’t work but the only way to be sure it will work is to let it run
and check errors.</p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image4-ff19345fa2d7541f4044b2309950fd7c.jpg" width="700" height="484" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-if-pipelines-could-be-just-code">What if pipelines could be… just code<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#what-if-pipelines-could-be-just-code" class="hash-link" aria-label="Direct link to What if pipelines could be… just code" title="Direct link to What if pipelines could be… just code" translate="no">​</a></h2>
<p>Good news is that they can! Pipelines can be just code working in a
standardized way, who knows maybe even using <code>docker</code> under the hood,
but more on that later.</p>
<p>Why would we want pipelines to be written in a programming language?</p>
<ul>
<li class="">creating and running tests with <em>actual</em> testing
frameworks</li>
<li class="">locally debugging and testing each pipeline
step</li>
<li class="">pipeline code versioning and releases</li>
<li class="">linting and code support (including various
copilots) inside and IDE or text editor</li>
<li class="">using all the more programming languages facilities;
async calls, functions, data structures, and more</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-the-deal-with-docker">What’s the deal with docker<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#whats-the-deal-with-docker" class="hash-link" aria-label="Direct link to What’s the deal with docker" title="Direct link to What’s the deal with docker" translate="no">​</a></h2>
<p>I mention earlier that having <code>docker</code> could be nice. This is because
when running pipelines we are bound to the proprietary runners offered
to us by 3rd party vendors such as GitHub, GitLab, but also tools like
Jenkins mandate their own syntax and integration points.</p>
<p>Wouldn’t it be cool if we could run the whole pipeline <strong>anywhere</strong> with
reproducibility guarantees? For this to happen we need a standard way of
executing pipeline jobs and steps/tasks. Here is where <code>docker</code> comes
in.</p>
<p>Instead of running <strong>all</strong> pipeline steps in a proprietary runner
format, we just need to run <strong>one</strong> step to hook into the runner’s
execution environment and let the containerized environment do the rest.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-dagger-and-how-it-can-help">What is dagger and how it can help<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#what-is-dagger-and-how-it-can-help" class="hash-link" aria-label="Direct link to What is dagger and how it can help" title="Direct link to What is dagger and how it can help" translate="no">​</a></h2>
<p>Sadly, <a href="https://dagger.io/" target="_blank" rel="noopener noreferrer" class="">dagger</a> has nothing to do with beautiful
blades but more do to with
<a href="https://en.wikipedia.org/wiki/Directed_acyclic_graph" target="_blank" rel="noopener noreferrer" class="">DAG</a> (Directed
acyclic graph).</p>
<p>So less of a:</p>
<p><img decoding="async" loading="lazy" alt="Photo by Jimmy
Chang
on
Unsplash" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image5-5c5965f4dcbdb91b2ec8593b82e39d14.jpg" width="700" height="525" class="img_ev3q"></p>
<p>And more like:</p>
<p><img decoding="async" loading="lazy" alt="Source: dagger website https://dagger.io/" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image6-a1fa3c32fb0393cebaeb66566343197e.jpg" width="1000" height="418" class="img_ev3q"></p>
<blockquote>
<p>Transform your Messy CI Scripts into <strong>Clean Code</strong></p>
<p>Powerful, programmable <strong>open source</strong> CI/CD engine that runs your
pipelines in containers — pre-push on your local machine and/or
post-push in CI</p>
</blockquote>
<h1>Let’s convert a pipeline</h1>
<p>We are going to convert an actual python pipeline of one of my projects
<a href="https://github.com/Piotr1215/killercoda-cli" target="_blank" rel="noopener noreferrer" class="">killercoda-cli</a> which is a
simple Python CLI helping with writing killercoda.com scenarios. The
goal is to convert just the right amount of steps and introduce dagger
gradually to the project.</p>
<p>The pipeline builds the CLI, runs tests and enables manual push to PyPi
registry.</p>
<p><a href="https://github.com/Piotr1215/killercoda-cli/blob/main/.github/workflows/ci.yml" target="_blank" rel="noopener noreferrer" class="">https://github.com/Piotr1215/killercoda-cli/blob/main/.github/workflows/ci.yml</a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="first-things-first-prerequisites">First things first; prerequisites<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#first-things-first-prerequisites" class="hash-link" aria-label="Direct link to First things first; prerequisites" title="Direct link to First things first; prerequisites" translate="no">​</a></h2>
<p>Before we start, we need to <a href="https://docs.dagger.io/quickstart/cli" target="_blank" rel="noopener noreferrer" class="">install dagger
CLI</a> and
<a href="https://www.docker.com/get-started/" target="_blank" rel="noopener noreferrer" class="">docker</a> (podman and nerdctl would
work too).</p>
<p>I’m using Linux, so <code>curl</code> will do just fine (with modified path to save
the binary to):</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token function" style="color:rgb(80, 250, 123)">curl</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-L</span><span class="token plain"> https://dl.dagger.io/dagger/install.sh </span><span class="token operator">|</span><span class="token plain"> </span><span class="token assign-left variable" style="color:rgb(189, 147, 249);font-style:italic">BIN_DIR</span><span class="token operator">=</span><span class="token plain">/usr/local/bin </span><span class="token function" style="color:rgb(80, 250, 123)">sh</span><br></span></code></pre></div></div>
<p>The installation script instructs me how to add completion; executing</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">dagger completion </span><span class="token function" style="color:rgb(80, 250, 123)">zsh</span><span class="token plain"> </span><span class="token operator">&gt;</span><span class="token plain"> /usr/local/share/zsh/site-functions/_dagger</span><br></span></code></pre></div></div>
<p>and after reloading <code>zshrc</code> tab completion works just fine:</p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image7-5745637db7ae93ed869a9a75f7ed2ea6.jpg" width="700" height="266" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="adding-dagger-to-the-project">Adding dagger to the project<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#adding-dagger-to-the-project" class="hash-link" aria-label="Direct link to Adding dagger to the project" title="Direct link to Adding dagger to the project" translate="no">​</a></h2>
<p>Running <code>dagger init --sdk=python</code> pulled dagger image, created a
<em>dagger</em> directory and <em>dagger.json</em> file.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">dagger</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── pyproject.toml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── requirements.lock</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">├── sdk</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── codegen</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── pyproject.toml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   ├── requirements.lock</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   │   └── src</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   │       └── codegen</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   │           ├── cli.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   │           ├── generator.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   │           ├── __init__.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   │           └── __main__.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── LICENSE</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── pyproject.toml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   ├── README.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│   └── src</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│       └── dagger</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── client</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── base.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── _core.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── gen.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── _guards.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── __init__.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   └── _session.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── _config.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── _connection.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── _engine</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── conn.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── download.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── __init__.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── progress.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── session.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   └── _version.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── _exceptions.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── __init__.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── log.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── _managers.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── mod</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── _arguments.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── cli.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── _converter.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── _exceptions.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── __init__.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── _module.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── _resolver.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   ├── _types.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           │   └── _utils.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           ├── py.typed</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│           └── telemetry</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│               ├── attributes.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">│               └── __init__.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">└── src</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    └── main</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">        └── __init__.py</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token number">12</span><span class="token plain"> directories, </span><span class="token number">42</span><span class="token plain"> files</span><br></span></code></pre></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token punctuation" style="color:rgb(248, 248, 242)">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"name"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"killercoda-cli"</span><span class="token plain">,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"sdk"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"python"</span><span class="token plain">,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"source"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"dagger"</span><span class="token plain">,</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  </span><span class="token string" style="color:rgb(255, 121, 198)">"engineVersion"</span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">:</span><span class="token plain"> </span><span class="token string" style="color:rgb(255, 121, 198)">"v0.11.7"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token punctuation" style="color:rgb(248, 248, 242)">}</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="build-environment-container">Build Environment Container<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#build-environment-container" class="hash-link" aria-label="Direct link to Build Environment Container" title="Direct link to Build Environment Container" translate="no">​</a></h2>
<p>Let’s add a function to the <code>src-&gt;main-&gt;__init__.py</code> to create a
development environment to build and test our project.</p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image8-8e66b5dd618606cd2204a45bc13919b6.jpg" width="1000" height="704" class="img_ev3q"></p>
<p>This function builds an environment with all the dependencies required
by my application.</p>
<p>We can run it <code>dagger call build-env — source=.</code> and build an image.</p>
<blockquote>
<p>💡Notice the kebab-case naming convention in the CLI, <code>build_env</code>
becomes <code>build-env</code></p>
</blockquote>
<p>Earlier, we discussed local <strong>debugging</strong> and <strong>testing</strong>. Well, this is
a container, so we should be able to drop into it with a shell!</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">➜ dagger call build-env </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--source</span><span class="token operator">=</span><span class="token plain">. terminal </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--cmd</span><span class="token operator">=</span><span class="token plain">bash</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">root@grt1fshu1uc6c:/src</span><span class="token comment" style="color:rgb(98, 114, 164)"># ls -lah</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">total </span><span class="token number">2</span><span class="token plain">.1M</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxr-xr-x </span><span class="token number">14</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">16</span><span class="token plain">:21 </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">.</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxr-xr-x  </span><span class="token number">1</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">16</span><span class="token plain">:23 </span><span class="token punctuation" style="color:rgb(248, 248, 242)">..</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">1</span><span class="token plain">.3M May </span><span class="token number">26</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain">:39 .aider.chat.history.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root  23K May </span><span class="token number">26</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain">:39 .aider.input.history</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxr-xr-x  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K May </span><span class="token number">26</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain">:39 .aider.tags.cache.v3</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-r--r--  </span><span class="token number">2</span><span class="token plain"> root root  52K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">14</span><span class="token plain">:00 .coverage</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root  </span><span class="token number">116</span><span class="token plain"> Feb </span><span class="token number">10</span><span class="token plain"> </span><span class="token number">19</span><span class="token plain">:36 .coveragerc</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxrwxr-x  </span><span class="token number">9</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">16</span><span class="token plain">:21 .git</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxrwxr-x  </span><span class="token number">3</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Feb </span><span class="token number">10</span><span class="token plain"> </span><span class="token number">11</span><span class="token plain">:44 .github</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">3</span><span class="token plain">.4K Feb </span><span class="token number">10</span><span class="token plain"> </span><span class="token number">11</span><span class="token plain">:34 .gitignore</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxr-xr-x  </span><span class="token number">6</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">12</span><span class="token plain">:55 .pytest_cache</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxrwxr-x  </span><span class="token number">4</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">13</span><span class="token plain">:58 .venv-test</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">1</span><span class="token plain">.1K Feb </span><span class="token number">10</span><span class="token plain"> </span><span class="token number">11</span><span class="token plain">:39 LICENSE.txt</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">5</span><span class="token plain">.4K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">16</span><span class="token plain">:17 README.md</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwx------  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">14</span><span class="token plain">:51 _media</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxrwxr-x  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K May </span><span class="token number">31</span><span class="token plain"> </span><span class="token number">11</span><span class="token plain">:38 assets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root    </span><span class="token number">0</span><span class="token plain"> Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">14</span><span class="token plain">:44 costam.log</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">6</span><span class="token plain">.4K May </span><span class="token number">26</span><span class="token plain"> </span><span class="token number">14</span><span class="token plain">:42 coverage.xml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxr-xr-x  </span><span class="token number">4</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">14</span><span class="token plain">:45 dagger</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-r--r--  </span><span class="token number">2</span><span class="token plain"> root root  </span><span class="token number">102</span><span class="token plain"> Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">12</span><span class="token plain">:40 dagger.json</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxrwxr-x  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Feb </span><span class="token number">10</span><span class="token plain"> </span><span class="token number">20</span><span class="token plain">:21 dist</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxrwxr-x  </span><span class="token number">3</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K May </span><span class="token number">31</span><span class="token plain"> </span><span class="token number">11</span><span class="token plain">:32 killercoda_cli</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root 686K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">14</span><span class="token plain">:47 output.txt</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">-rw-rw-r--  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">2</span><span class="token plain">.5K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">14</span><span class="token plain">:15 pyproject.toml</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxrwxr-x  </span><span class="token number">2</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K May </span><span class="token number">31</span><span class="token plain"> </span><span class="token number">11</span><span class="token plain">:08 temp_template</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">drwxrwxr-x  </span><span class="token number">4</span><span class="token plain"> root root </span><span class="token number">4</span><span class="token plain">.0K Jun </span><span class="token number">13</span><span class="token plain"> </span><span class="token number">14</span><span class="token plain">:03 tests</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">root@grt1fshu1uc6c:/src</span><span class="token comment" style="color:rgb(98, 114, 164)">#</span><br></span></code></pre></div></div>
<p>Being able to locally check the container is a game changer. Remember,
the same will run on a remote runner VM.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="running-tests">Running Tests<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#running-tests" class="hash-link" aria-label="Direct link to Running Tests" title="Direct link to Running Tests" translate="no">​</a></h2>
<p>One more function for running tests, notice how we execute the
<code>build_environment</code> function before running tests.</p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image9-64d7dd2751dc41a7a8f44e746f28045d.jpg" width="1000" height="552" class="img_ev3q"></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">➜ dagger call </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">test</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--source</span><span class="token operator">=</span><span class="token plain">.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Current directory: /tmp/test_generate_assets/killercoda-assets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Source directory: /tmp/test_generate_assets/killercoda-assets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Generating assets from template: https://github.com/Piotr1215/cookiecutter-killercoda-assets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Output directory: /tmp/test_generate_assets</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">Assets generated successfully.</span><br></span></code></pre></div></div>
<p>Tests pass however outpout is a bit sparse, earlier we have setup tab
completion, let’s see if there are any flags that can help us <code>dagger call test — source=. &lt;TAB&gt;</code></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">➜ dagger call </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">test</span><span class="token plain"> </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--source</span><span class="token operator">=</span><span class="token plain">. </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--debug</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--debug</span><span class="token plain">     -- show debug logs and full verbosity</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--json</span><span class="token plain">      -- Present result as JSON</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--mod</span><span class="token plain">       -- Path to dagger.json config </span><span class="token function" style="color:rgb(80, 250, 123)">file</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> the module or a directory containing that file. Either </span><span class="token builtin class-name" style="color:rgb(189, 147, 249)">local</span><span class="token plain"> path </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">e.g. </span><span class="token string" style="color:rgb(255, 121, 198)">"/path/to/some/dir"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"> or a github repo </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">e.g. </span><span class="token string" style="color:rgb(255, 121, 198)">"github.com/dagger/dagger/path/to/some/subdir"</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--output</span><span class="token plain">    -- Path </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">in</span><span class="token plain"> the </span><span class="token function" style="color:rgb(80, 250, 123)">host</span><span class="token plain"> to save the result to</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--progress</span><span class="token plain">  -- progress output </span><span class="token function" style="color:rgb(80, 250, 123)">format</span><span class="token plain"> </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">auto, plain, </span><span class="token function" style="color:rgb(80, 250, 123)">tty</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--silent</span><span class="token plain">    -- disable terminal UI and progress output</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain"></span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--verbose</span><span class="token plain">   -- increase verbosity </span><span class="token punctuation" style="color:rgb(248, 248, 242)">(</span><span class="token plain">use </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-vv</span><span class="token plain"> or </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">-vvv</span><span class="token plain"> </span><span class="token keyword" style="color:rgb(189, 147, 249);font-style:italic">for</span><span class="token plain"> </span><span class="token function" style="color:rgb(80, 250, 123)">more</span><span class="token punctuation" style="color:rgb(248, 248, 242)">)</span><br></span></code></pre></div></div>
<p>The debug option gives us full run logs with max verbosity, great!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="build--publish">Build &amp; Publish<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#build--publish" class="hash-link" aria-label="Direct link to Build &amp; Publish" title="Direct link to Build &amp; Publish" translate="no">​</a></h2>
<p>The last two functions are <code>publish</code> and <code>build</code>. Publish is going to
use the awesome <a href="http://ttl.sh/" target="_blank" rel="noopener noreferrer" class="">ttl.sh</a> service, which allows for
publishing short-lived images (max 24h) and is great for testing. Build
will perform a multistaged build and get our application ready for
deployment.</p>
<p><img decoding="async" loading="lazy" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image10-7a34f2c966c19dad6279a4dbd35394ad.jpg" width="1000" height="667" class="img_ev3q"></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">➜ dagger call publish </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--source</span><span class="token operator">=</span><span class="token plain">.</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">ttl.sh/killercoda-cli-15186746@sha256:6d2e22543154fff996e3d829450953981c60aba6f161477e5aa3e00d3faaa2cb</span><br></span></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="integrate-with-github-actions">Integrate with GitHub Actions<a href="https://frosty-babbage-3125a3.netlify.app/blog/building-pipelines-dagger#integrate-with-github-actions" class="hash-link" aria-label="Direct link to Integrate with GitHub Actions" title="Direct link to Integrate with GitHub Actions" translate="no">​</a></h2>
<p>The <a href="https://github.com/marketplace/actions/dagger-for-github" target="_blank" rel="noopener noreferrer" class="">integration with GitHub
Actions</a> is
easy, just add the action to YAML workflow and call any dagger function</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#F8F8F2;--prism-background-color:#282A36"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#F8F8F2;background-color:#282A36"><code class="codeBlockLines_e6Vv"><span class="token-line" style="color:#F8F8F2"><span class="token plain">- name: Hello</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  uses: dagger/dagger-for-github@v5</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">  with:</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    verb: call</span><br></span><span class="token-line" style="color:#F8F8F2"><span class="token plain">    args: call publish </span><span class="token parameter variable" style="color:rgb(189, 147, 249);font-style:italic">--source</span><span class="token operator">=</span><span class="token plain">.</span><br></span></code></pre></div></div>
<h1>Closing Thoughts</h1>
<p>Integrating dagger into my GitHub Actions workflow wasn’t super easy,
but it was way easier than dealing with pure YAML. The ability to
<strong>adapt</strong> only parts of the workflow is great, no need for
all-or-nothing rewrites; small, incremental steps are just fine.</p>
<p>From a high level, the below diagram shows working with dagger locally
and in a remote environment.</p>
<p><img decoding="async" loading="lazy" alt="dagger workflow" src="https://frosty-babbage-3125a3.netlify.app/assets/images/image11-3d96a5a43bb93f0435fce5b3790bebab.jpg" width="1000" height="461" class="img_ev3q"></p>
<p>A bit win, in my opinion, is that if tomorrow I will decide to move to
GitLab, I can do it much easier. Instead of migrating the whole
pipeline, I keep it as is and migrate only the <strong>entry-point</strong>.</p>
<p>With the right amount of abstractions and clever reuse of the docker
engine, dagger is a very strong choice for any pipeline. The community
can collaborate on functions and capture best practices, which are
available on <a href="https://daggerverse.dev/" target="_blank" rel="noopener noreferrer" class=""><strong>daggerverse</strong></a>.</p>
<p>Give dagger a try and let me know what are your experiences.</p>
<p>Thanks for taking the time to read this post. I hope you found it
interesting and informative.</p>
<p>🔗 <strong>Connect with me on</strong>
<a href="https://www.linkedin.com/in/piotr-zaniewski/" target="_blank" rel="noopener noreferrer" class=""><strong>LinkedIn</strong></a></p>
<p>🌐 <strong>Visit my</strong> <a href="https://cloudrumble.net/" target="_blank" rel="noopener noreferrer" class=""><strong>Website</strong></a></p>
<p>📺 <strong>Subscribe to my</strong> <a href="https://www.youtube.com/@cloud-native-corner" target="_blank" rel="noopener noreferrer" class=""><strong>YouTube
Channel</strong></a></p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[cloud-native-platform-kubernetes]]></title>
            <link>https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes</link>
            <guid>https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes</guid>
            <pubDate>Wed, 12 Jun 2024 07:49:17 GMT</pubDate>
            <description><![CDATA[title: "How to Build Cloud Native Platforms with Kubernetes"]]></description>
            <content:encoded><![CDATA[<p>title: "How to Build Cloud Native Platforms with Kubernetes"
date: 2024-06-12
tags: ['cloud-native', 'kubernetes', 'platrform-engineering']</p>
<!-- -->
<p><img decoding="async" loading="lazy" alt="header-image" src="https://frosty-babbage-3125a3.netlify.app/assets/images/header-image-fe465ce49b06557d76243f5d1a13b0ff.png" width="2000" height="1125" class="img_ev3q"></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="developer-portals-gitops-best-practices">Developer Portals, GitOps, Best Practices<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#developer-portals-gitops-best-practices" class="hash-link" aria-label="Direct link to Developer Portals, GitOps, Best Practices" title="Direct link to Developer Portals, GitOps, Best Practices" translate="no">​</a></h3>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h2>
<p>Platform Engineering focuses on empowering developers and organizations by creating and maintaining internal software products known as platforms. In this blog, we will explore what platforms are, why they are important, and uncover best practices for creating and maintaining well-architected platforms.</p>
<p>This content will be valuable for Platform Engineers, Architects, DevOps Specialists, or anyone curious about how platforms can drive innovation and efficiency in development.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="understanding-platform-categories-mapping-the-terrain">Understanding Platform Categories: Mapping the Terrain<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#understanding-platform-categories-mapping-the-terrain" class="hash-link" aria-label="Direct link to Understanding Platform Categories: Mapping the Terrain" title="Direct link to Understanding Platform Categories: Mapping the Terrain" translate="no">​</a></h3>
<p>Similar to DevOps, Platform Engineering struggles to define a platform concisely. A good way to understand what a platform is, is to list various kinds of platforms and their characteristics.</p>
<p><img decoding="async" loading="lazy" src="https://cdn-images-1.medium.com/max/3062/1*cRQ7MFBTxnZA-xORwNlciQ.png" alt="Platform Types" class="img_ev3q"><em>Platform Types</em></p>
<ul>
<li class="">
<p><strong>Business as a Platform:</strong>
Consider Uber, the entire product is a platform that connects users and drivers. This platform creates an ecosystem where businesses operate, users engage, and interactions happen seamlessly.</p>
</li>
<li class="">
<p><strong>Domain-Specific Platforms:</strong>
These platforms provide cross-cutting functionality for other applications. An example could be a geolocation API that is consumed by web frontend, mobile app and other services.</p>
</li>
<li class="">
<p><strong>Domain-Agnostic Platforms:</strong>
These platforms serve as foundational building blocks for developers, offering essential tools like database management, cloud storage, and user authentication. Cloud platforms like AWS or Azure provide the infrastructure and services that countless digital products rely on and are a good mental model to have when designing our own cloud native platform.</p>
</li>
</ul>
<p>The platform landscape is vast and varied. From business-centric models to specialized domain platforms and versatile tools for developers, each plays a pivotal role in the digital ecosystem.</p>
<p>In this blog, we will focus on <strong>domain-agnostic platforms</strong> providing infrastructure.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-case-for-cloud-native-platforms">The Case for Cloud-Native Platforms<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#the-case-for-cloud-native-platforms" class="hash-link" aria-label="Direct link to The Case for Cloud-Native Platforms" title="Direct link to The Case for Cloud-Native Platforms" translate="no">​</a></h3>
<blockquote>
<h1>Cloud-native is about how applications are created and deployed, not where. — Priyanka Sharma</h1>
</blockquote>
<p>Cloud-native platforms provide a foundation that allows applications to be designed with flexibility, largely making them environment agnostic. A well-architected platform offers several key benefits:</p>
<ul>
<li class="">
<p><strong>Simplified infrastructure management:</strong>
Infrastructure provisioning and management are abstracted in a way that enables developers to move faster without compromising security and compliance requirements.</p>
</li>
<li class="">
<p><strong>Increased development efficiency:</strong>
A well-designed platform should increase developers productivity, improving metrics like lowering <strong>time to first commit</strong>, improving the <strong>incidents-to-resolution</strong> or reducing <strong>time to onboard</strong> new developers.</p>
</li>
<li class="">
<p>**Built-in scalability and reliability:
**A successful platform brings to the table elements that are not part of core development efforts, but are crucial for product success. Those are: observability, scalability, automated rollbacks, integrated authentication and more.</p>
</li>
</ul>
<p><img decoding="async" loading="lazy" src="https://cdn-images-1.medium.com/max/4800/1*JAa19mGgLG37rktZ9enWzw.png" alt="Building Blocks of Cloud-Native Platforms" class="img_ev3q"><em>Building Blocks of Cloud-Native Platforms</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="self-service-portal">Self-service portal<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#self-service-portal" class="hash-link" aria-label="Direct link to Self-service portal" title="Direct link to Self-service portal" translate="no">​</a></h3>
<p>A self-service portal is a user-friendly interface that allows users to access and manage resources independently, empowering developers and users to create, configure, and deploy resources without IT support. This streamlines workflows, accelerates project timelines, and enhances productivity.</p>
<p>Examples like <a href="https://backstage.io/" target="_blank" rel="noopener noreferrer" class="">Backstage</a>, developed by Spotify, and <a href="https://getport.io/" target="_blank" rel="noopener noreferrer" class="">Port</a> provide customizable interfaces for managing developer tools and services, ensuring efficient and consistent interactions. These portals embody the essence of self-service, enabling quick, autonomous actions that reduce bottlenecks and foster agility in development processes.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="programmatic-apis">Programmatic APIs<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#programmatic-apis" class="hash-link" aria-label="Direct link to Programmatic APIs" title="Direct link to Programmatic APIs" translate="no">​</a></h3>
<p>Programmatic APIs are the backbone of cloud-native platforms, enabling seamless interaction with platform services and functionalities. These APIs allow developers to automate tasks, integrate different services, and build complex workflows, enhancing efficiency and consistency across environments.</p>
<p>APIs provide programmatic access to essential platform features, allowing developers to automate repetitive tasks and streamline operations. They support various transport mechanisms such as <a href="https://restfulapi.net/" target="_blank" rel="noopener noreferrer" class="">REST</a>, <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP" target="_blank" rel="noopener noreferrer" class="">HTTP</a>, and <a href="https://grpc.io/" target="_blank" rel="noopener noreferrer" class="">gRPC</a>, offering flexibility in how services communicate. For instance, API based on <a href="https://cloud.google.com/blog/topics/developers-practitioners/build-platform-krm-part-2-how-kubernetes-resource-model-works" target="_blank" rel="noopener noreferrer" class="">Kubernetes Resource Model</a> enables developers to manage containerized applications, while <a href="https://aws.amazon.com/tools/" target="_blank" rel="noopener noreferrer" class="">AWS SDKs</a> facilitate interactions with a wide range of cloud resources. By leveraging programmatic APIs, platforms ensure that developers can efficiently build, deploy, and manage applications, driving productivity and innovation.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="automated-workflows">Automated Workflows<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#automated-workflows" class="hash-link" aria-label="Direct link to Automated Workflows" title="Direct link to Automated Workflows" translate="no">​</a></h3>
<p>Automated workflows are crucial for provisioning and deployment processes in cloud-native platforms. They ensure tasks are executed consistently and efficiently, minimizing human error and enhancing productivity.</p>
<p>Key to these workflows are CI/CD pipelines, which automate the build, test, and deployment stages of application development. Tools like <a href="https://argoproj.github.io/cd/" target="_blank" rel="noopener noreferrer" class="">Argo CD</a> and <a href="https://fluxcd.io/" target="_blank" rel="noopener noreferrer" class="">Flux</a> enable <a href="https://opengitops.dev/" target="_blank" rel="noopener noreferrer" class="">GitOps</a> practices, where infrastructure and application updates are managed through Git repositories. By leveraging automated workflows, platforms can ensure rapid, reliable deployments, maintain consistency across environments, and accelerate the overall development process.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="monitoring-and-observability">Monitoring and Observability<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#monitoring-and-observability" class="hash-link" aria-label="Direct link to Monitoring and Observability" title="Direct link to Monitoring and Observability" translate="no">​</a></h3>
<p>Monitoring and observability tools provide crucial insights into the performance and health of cloud-native platforms. These tools help detect issues early, understand system behavior, and ensure applications run smoothly.</p>
<p>Prominent tools include <a href="https://prometheus.io/" target="_blank" rel="noopener noreferrer" class="">Prometheus</a> for collecting and querying metrics, <a href="https://grafana.com/" target="_blank" rel="noopener noreferrer" class="">Grafana</a> for visualizing data and creating dashboards, and <a href="https://opentelemetry.io/" target="_blank" rel="noopener noreferrer" class="">OpenTelemetry</a> for tracing and observability. Together, they enable proactive management of resources, quick resolution of issues, and comprehensive visibility into system performance. By integrating these tools, platforms can maintain high availability and performance, ensuring a seamless user experience.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="security-and-governance-controls">Security and Governance Controls<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#security-and-governance-controls" class="hash-link" aria-label="Direct link to Security and Governance Controls" title="Direct link to Security and Governance Controls" translate="no">​</a></h3>
<p>Integrated security and governance controls are vital for maintaining compliance and protecting sensitive data in cloud-native platforms. These controls ensure that platform operations adhere to security policies and regulatory requirements.</p>
<p>Tools like <a href="https://github.com/open-policy-agent/gatekeeper" target="_blank" rel="noopener noreferrer" class="">OPA GateKeeper</a>, <a href="https://kyverno.io/" target="_blank" rel="noopener noreferrer" class="">Kyverno</a>, and <a href="https://falco.org/" target="_blank" rel="noopener noreferrer" class="">Falco</a> play a crucial role in enforcing security policies, managing configurations, and detecting anomalies. OPA GateKeeper and Kyverno help in policy enforcement and compliance, while Falco specializes in runtime security and intrusion detection. By incorporating these tools, platforms can ensure robust security, maintain compliance, and mitigate risks effectively.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="ever-evolving">Ever Evolving<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#ever-evolving" class="hash-link" aria-label="Direct link to Ever Evolving" title="Direct link to Ever Evolving" translate="no">​</a></h3>
<blockquote>
<h1>The only constant in technology is change. — Marc Benioff</h1>
</blockquote>
<p>Developer platforms are constantly evolving to meet the changing needs of developers and users alike. This continuous evolution ensures that platforms remain relevant, efficient, and capable of supporting the latest innovations and best practices. By staying adaptable and forward-thinking, platforms can provide the tools and features necessary to drive ongoing success and innovation.</p>
<p><img decoding="async" loading="lazy" src="https://cdn-images-1.medium.com/max/4800/1*V-8lBAFNUGURu4q-PSLD_g.png" alt="" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="embracing-kubernetes-resource-model-apis">Embracing Kubernetes Resource Model APIs<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#embracing-kubernetes-resource-model-apis" class="hash-link" aria-label="Direct link to Embracing Kubernetes Resource Model APIs" title="Direct link to Embracing Kubernetes Resource Model APIs" translate="no">​</a></h2>
<blockquote>
<p><em>Application Programming <strong>Interface</strong>, is a set of rules and protocols for building and <strong>interacting</strong> with software.</em></p>
</blockquote>
<p>The Kubernetes Resource Model API is the industry standard for managing resources in a cloud-native environment. Kubernetes acts as a universal control plane, continuously reconciling the desired state with the actual state of the system. Standardizing on this model offers several key benefits:</p>
<ol>
<li class="">
<p><strong>Industry-Wide Standardization:</strong> Kubernetes has become the de facto standard for cloud-native infrastructure management. Its API-driven approach is widely adopted, ensuring compatibility and ease of integration with various tools and services.</p>
</li>
<li class="">
<p><strong>Universal Control Plane:</strong> Kubernetes serves as a universal control plane, providing a centralized management interface for infrastructure and applications. This centralization simplifies operations and enforces consistency across environments.</p>
</li>
<li class="">
<p><strong>Continuous Reconciliation:</strong> The Kubernetes API supports declarative management, where the desired state of resources is defined, and Kubernetes continuously reconciles this state. This automated reconciliation reduces manual intervention and ensures system reliability.</p>
</li>
<li class="">
<p><strong>Separation of Concerns:</strong> Platform engineers can configure infrastructure and policies, while developers interact with higher-level APIs. This separation enhances automation and self-service capabilities, empowering developers without compromising security or compliance.</p>
</li>
<li class="">
<p><strong>Scalability and Extensibility:</strong> Supporting transport mechanisms like REST, HTTP, and gRPC, the Kubernetes API is adaptable and scalable. It integrates seamlessly with a wide range of tools, facilitating the growth and evolution of the platform.</p>
</li>
</ol>
<p>By leveraging Kubernetes Resource Model APIs, organizations can build robust, scalable, and efficient platforms that meet the dynamic needs of modern development environments​.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="platform-as-a-product-a-new-perspective">Platform as a Product: A New Perspective<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#platform-as-a-product-a-new-perspective" class="hash-link" aria-label="Direct link to Platform as a Product: A New Perspective" title="Direct link to Platform as a Product: A New Perspective" translate="no">​</a></h3>
<p>Adopting a product approach to platform engineering is crucial for creating successful internal platforms. This means focusing on delivering continuous value to users — developers and the organization. It involves understanding user needs, designing and testing solutions, implementing them, and gathering feedback for continuous improvement.</p>
<p>Cloud hyperscalers like AWS, Google Cloud, and Microsoft Azure exemplify this approach. They have built user-centric platforms that are constantly updated with new features, driven by user feedback and accessible via standardized APIs. This ensures they remain relevant and valuable.</p>
<p>For internal platforms, roles such as <strong>product owners</strong> and <strong>project managers</strong> are essential. They help ensure the platform evolves in response to developer needs, maintaining usability and effectiveness. By treating your internal platform as a product, you create a sustainable resource tailored to your organization’s unique needs.</p>
<p><img decoding="async" loading="lazy" src="https://cdn-images-1.medium.com/max/4800/1*Fn_oNGQ2SkF6AYNibl5vSQ.png" alt="Platform as a Product" class="img_ev3q"><em>Platform as a Product</em></p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="delivering-value-through-cloud-native-platforms">Delivering Value Through Cloud-Native Platforms<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#delivering-value-through-cloud-native-platforms" class="hash-link" aria-label="Direct link to Delivering Value Through Cloud-Native Platforms" title="Direct link to Delivering Value Through Cloud-Native Platforms" translate="no">​</a></h3>
<!--$--><video style="display:block;width:100%;height:400px;max-width:100%;margin:0 auto" controls=""></video><!--/$-->
<p>In our demo video, we showcase how to build a platform that embodies key cloud-native principles. This practical example demonstrates the immense value that a well-architected cloud-native platform can deliver. Here’s a brief overview of what you can expect:</p>
<ul>
<li class="">
<p><strong>Empowering Developers:</strong> See how the platform provides developers with the tools and autonomy they need to innovate and deliver faster.</p>
</li>
<li class="">
<p><strong>Cloud-Native Principles:</strong> Watch as we leverage containerization, microservices, and other cloud-native practices to build a robust, scalable platform.</p>
</li>
<li class="">
<p><strong>API-Driven Approach:</strong> Discover how using programmatic APIs streamlines operations, enhances automation, and ensures seamless integration between services.</p>
</li>
<li class="">
<p><strong>GitOps Workflow:</strong> Learn how the platform employs GitOps practices to manage infrastructure as code, enabling more efficient and reliable deployments.</p>
</li>
</ul>
<p>Watch the video to see these principles in action and understand how they come together to create a powerful, developer-centric platform.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="essential-tools">Essential Tools<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#essential-tools" class="hash-link" aria-label="Direct link to Essential Tools" title="Direct link to Essential Tools" translate="no">​</a></h3>
<p>In the demo, you can see a range of tools that form the backbone of cloud-native platforms, each serving a critical role. From Kubernetes as the control plane orchestrator to GitHub for managing API calls via pull requests, these tools collectively ensure efficient, scalable, and secure infrastructure management.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="recap-platform-components-in-action">Recap: Platform Components in Action<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#recap-platform-components-in-action" class="hash-link" aria-label="Direct link to Recap: Platform Components in Action" title="Direct link to Recap: Platform Components in Action" translate="no">​</a></h3>
<p>Let’s recap what we’ve learned about using Kubernetes as a control plane for infrastructure provisioning:</p>
<ol>
<li class="">
<p><strong>Self-Service Portal:</strong> The Developer accesses the IDP Portal for a unified UI experience to manage applications and infrastructure.</p>
</li>
<li class="">
<p><strong>Push Changes:</strong> The Developer pushes changes to the GitOps Repository via a pull request.</p>
</li>
<li class="">
<p><strong>Approval and Merge:</strong> The Platform Engineer reviews, approves, and merges the pull request, updating configurations.</p>
</li>
<li class="">
<p><strong>Sync Changes:</strong> The GitOps Repository syncs the changes to ArgoCD.</p>
</li>
<li class="">
<p><strong>Deploy Changes:</strong> ArgoCD deploys the changes to the Kubernetes API.</p>
</li>
<li class="">
<p><strong>Reconcile Infrastructure:</strong> The Kubernetes API reconciles the infrastructure via Crossplane.</p>
</li>
<li class="">
<p><strong>Provision Infrastructure:</strong> Crossplane provisions the infrastructure via various providers.</p>
</li>
</ol>
<p>This sequence ensures a streamlined, automated process for managing and provisioning infrastructure using Kubernetes and GitOps principles.</p>
<p><img decoding="async" loading="lazy" alt="platform-components" src="https://frosty-babbage-3125a3.netlify.app/assets/images/platform-components-de1fed15ff882994b3dc2ece4e6178ca.png" width="1920" height="629" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="closing-thoughts">Closing Thoughts<a href="https://frosty-babbage-3125a3.netlify.app/blog/cloud-native-platform-kubernetes#closing-thoughts" class="hash-link" aria-label="Direct link to Closing Thoughts" title="Direct link to Closing Thoughts" translate="no">​</a></h2>
<p>Cloud-native platforms are revolutionizing how we develop and manage applications by providing robust, scalable, and secure environments. They empower developers with self-service portals, streamline operations with programmatic APIs, and ensure reliability through automated workflows and comprehensive monitoring tools. By embracing these platforms, organizations can accelerate innovation, enhance productivity, and maintain high standards of security and compliance.</p>
<p>Treating platforms as products ensures continuous improvement and alignment with user needs, making them indispensable tools in today’s fast-paced tech landscape. Whether you’re a Platform Engineer, Architect, or DevOps Specialist, leveraging cloud-native platforms can drive significant value, fostering a culture of efficiency and agility. Stay ahead of the curve, explore the potential of cloud-native platforms, and watch your organization thrive.</p>
<p><img decoding="async" loading="lazy" src="https://cdn-images-1.medium.com/max/2000/1*ADSeAEG6eEqlieeEncCD_g.jpeg" alt="" class="img_ev3q"></p>
<p>Thanks for taking the time to read this post. I hope you found it interesting and informative.</p>
<p>🔗 <strong>Connect with me on <a href="https://www.linkedin.com/in/piotr-zaniewski/" target="_blank" rel="noopener noreferrer" class="">LinkedIn</a></strong></p>
<p>🌐 <strong>Visit my <a href="https://cloudrumble.net/" target="_blank" rel="noopener noreferrer" class="">Website</a></strong></p>
<p>📺 <strong>Subscribe to my <a href="https://www.youtube.com/@cloud-native-corner" target="_blank" rel="noopener noreferrer" class="">YouTube Channel</a></strong></p>]]></content:encoded>
        </item>
    </channel>
</rss>