<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nerav Doshi</title>
    <description>The latest articles on DEV Community by Nerav Doshi (@agenticdevops).</description>
    <link>https://dev.to/agenticdevops</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3916785%2F423b2322-f2d4-4fee-8576-b0537c2866f0.png</url>
      <title>DEV Community: Nerav Doshi</title>
      <link>https://dev.to/agenticdevops</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/agenticdevops"/>
    <language>en</language>
    <item>
      <title>Containerized Ollama and Found the Real Memory Overhead</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Wed, 09 Sep 2026 00:47:46 +0000</pubDate>
      <link>https://dev.to/agenticdevops/containerized-ollama-and-found-the-real-memory-overhead-j87</link>
      <guid>https://dev.to/agenticdevops/containerized-ollama-and-found-the-real-memory-overhead-j87</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; On Linux, containers run natively — they share the host's kernel directly. On macOS, they can't (containers need a Linux kernel underneath), so tools like Podman and Docker Desktop quietly run a small Linux virtual machine in the background and put your containers inside &lt;em&gt;that&lt;/em&gt;. Every container on a Mac is really sharing a fixed slice of memory carved out for that VM, not your Mac's full RAM — which matters a lot once something inside the container needs more memory than the VM itself was given.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ran:&lt;/strong&gt; Started Ollama as a container with Podman, exposed on a different port (11435) than the native app already running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;--name&lt;/span&gt; ollama-container &lt;span class="nt"&gt;-p&lt;/span&gt; 11435:11434 ollama/ollama
podman &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; ollama-container ollama pull llama3.2:1b
podman &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; ollama-container ollama run llama3.2:1b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pull succeeded (after one transient network retry on the first attempt). Loading the model didn't:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Error: 500 Internal Server Error: model requires more system memory (1.3 GiB) than is available (620.8 MiB)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;podman machine list&lt;/code&gt; showed why — the VM backing every Podman container on this machine was set to just &lt;strong&gt;2GiB total&lt;/strong&gt;, for the OS, runtime, and every container combined. After host overhead, only ~620MB was actually free — well under the ~1.3GB this model needs.&lt;/p&gt;

&lt;p&gt;Fixed by resizing the VM itself (has to be stopped first):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman machine stop
podman machine &lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;--memory&lt;/span&gt; 4096
podman machine start
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restarting the VM also stopped the container, which then wouldn't &lt;code&gt;exec&lt;/code&gt; into ("container state improper") until explicitly restarted:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman start ollama-container
podman &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; ollama-container ollama run llama3.2:1b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That worked — asked it "what is kubernetes?" to force a real response, then checked memory with Podman's equivalent of &lt;code&gt;docker stats&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;podman stats ollama-container &lt;span class="nt"&gt;--no-stream&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Entry 01 (bare metal, native Ollama app)&lt;/th&gt;
&lt;th&gt;Containerized (Podman)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Process memory&lt;/td&gt;
&lt;td&gt;~1.24 GB RSS&lt;/td&gt;
&lt;td&gt;1.663 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Processor&lt;/td&gt;
&lt;td&gt;100% GPU (Metal)&lt;/td&gt;
&lt;td&gt;CPU only (no Metal passthrough in a container)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The container used about &lt;strong&gt;34% more memory&lt;/strong&gt; than the same model running natively — 1.663GB vs. 1.24GB. Some of that gap is Podman/Ollama server overhead inside the container; some is likely the lack of GPU acceleration forcing more CPU-side memory use, though that's an inference from the numbers rather than something directly measured here — worth a more controlled comparison in a future entry rather than treating it as confirmed. Either way, "same model, same memory" turned out to be false: containerizing an inference workload isn't memory-neutral.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; The actual resource request to set for a containerized version of this model should be closer to 1.663GB than the 1.24GB bare-metal number from Entry 01 — the container overhead is real, not negligible, and sizing a Kubernetes pod off bare-metal numbers alone would under-provision it. Also worth remembering for local dev: Podman's VM has its own fixed memory ceiling independent of your Mac's total RAM, and that's the first thing to check before assuming a model itself is too large to run.&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>podman</category>
      <category>containers</category>
      <category>resourceplanning</category>
    </item>
    <item>
      <title>Wired the Local MCP Server into Claude Code</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Wed, 02 Sep 2026 19:58:27 +0000</pubDate>
      <link>https://dev.to/agenticdevops/wired-the-local-mcp-server-into-claude-code-3e1f</link>
      <guid>https://dev.to/agenticdevops/wired-the-local-mcp-server-into-claude-code-3e1f</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; Every prior entry in this arc — building the Chroma index, chunking, querying — was done by hand, typing Python commands one at a time. An MCP server is what turns that into a real tool: a small program that exposes specific capabilities (like "search my notes") to an AI client such as Claude Code, so the client can call it directly instead of a person running queries manually. The point of this entry was proving that connection actually works, not just that the server starts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ran:&lt;/strong&gt; After the earlier &lt;code&gt;Connection closed&lt;/code&gt; error (Claude Code launching a bare &lt;code&gt;python3&lt;/code&gt;, which resolved to system Python outside the venv, missing every package the server needs), re-registered the server pointing directly at the venv's Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude mcp remove today-i-ran-notes
claude mcp add today-i-ran-notes &lt;span class="nt"&gt;--&lt;/span&gt; ~/mcp-env/bin/python3 mcp_search_server.py
claude mcp list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returned &lt;code&gt;✔ Connected&lt;/code&gt;. Then, in a real Claude Code session, asked it directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use the today-i-ran-notes server to search for how to check pod status with oc&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; The tool got called — twice. First a broad search, then a follow-up call pulling more detail from the closest match. Claude Code's own response is worth quoting closely, because it's more interesting than a clean success would have been:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The search returned results, but none of them directly cover checking pod status with oc. The closest match is a note about building an "oc CLI Mentor" system prompt... but doesn't appear to include specific pod-status commands.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's the right answer. Entry 02 is about building a constrained system prompt, not a list of pod-status commands — the tool didn't hallucinate a match, it accurately reported the gap and said so plainly, then fell back to its own general knowledge to actually answer the question, clearly separating "your notes don't cover this" from "here's the answer anyway."&lt;/p&gt;

&lt;p&gt;One more thing worth flagging clearly: when asked to save the new commands for future reference, Claude Code saved them to its own built-in memory system — not to the Chroma index built across Entries 05–07. Those are two separate mechanisms that are easy to conflate: one is the RAG pipeline this series has been building by hand, the other is Claude Code's own persistent memory feature. The note that got saved lives in the latter, not in &lt;code&gt;chroma_db&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; The full loop works — a local embeddings pipeline, exposed as a real MCP tool, correctly invoked by a real client, with honest reporting when the answer isn't in the index rather than a confident wrong guess. That honesty is the most important result here: a search tool that admits "not in here" is far more useful than one that always returns something and lets the caller assume it's relevant.&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>rag</category>
      <category>mcp</category>
      <category>chromadb</category>
    </item>
    <item>
      <title>What is DevOps? A Plain English Guide</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Wed, 02 Sep 2026 15:51:45 +0000</pubDate>
      <link>https://dev.to/agenticdevops/what-is-devops-a-plain-english-guide-2102</link>
      <guid>https://dev.to/agenticdevops/what-is-devops-a-plain-english-guide-2102</guid>
      <description>&lt;h2&gt;
  
  
  Ever Wondered How Netflix Never Seems to Go Down?
&lt;/h2&gt;

&lt;p&gt;Think about this for a second. Netflix has over 260 million subscribers worldwide. People are watching shows in Tokyo, London, Lagos, and New York — all at the same time. And yet, when was the last time Netflix crashed on you?&lt;/p&gt;

&lt;p&gt;Now think about your favourite food delivery app. You open it, order food, track your driver in real time, and get a notification the moment your burger arrives. All of that happens in seconds.&lt;/p&gt;

&lt;p&gt;Behind all of this is a way of working called DevOps. And by the end of this article, you'll understand exactly what it is — no jargon, no complicated diagrams, just plain English.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Old Way (And Why It Was a Nightmare)
&lt;/h2&gt;

&lt;p&gt;To understand DevOps, we first need to understand the problem it solved.&lt;/p&gt;

&lt;p&gt;Imagine a software company in the early 2000s. They had two completely separate teams:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Developers&lt;/strong&gt; — the people who wrote the code and built new features&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Operations team&lt;/strong&gt; — the people who managed the servers and kept everything running&lt;/p&gt;

&lt;p&gt;These two teams barely talked to each other. Developers would spend months building new features, then hand over a massive pile of code to the operations team and say "here you go, make it work."&lt;/p&gt;

&lt;p&gt;The operations team would panic. They hadn't been involved in building it, had no idea what it did, and now they had to deploy it to millions of users without breaking anything.&lt;/p&gt;

&lt;p&gt;The result? Deployments took weeks. Bugs slipped through. Systems crashed. Customers complained. And the two teams blamed each other.&lt;/p&gt;

&lt;p&gt;Sound stressful? It was.&lt;/p&gt;




&lt;h2&gt;
  
  
  So What is DevOps?
&lt;/h2&gt;

&lt;p&gt;DevOps is simply the practice of bringing developers and operations teams together to build, test, and release software faster and more reliably.&lt;/p&gt;

&lt;p&gt;The name itself is a combination of &lt;strong&gt;Dev&lt;/strong&gt; (Development) and &lt;strong&gt;Ops&lt;/strong&gt; (Operations). Instead of two teams working in silos, they work as one team with shared goals, shared tools, and shared responsibility.&lt;/p&gt;

&lt;p&gt;Think of it like a restaurant kitchen.&lt;/p&gt;

&lt;p&gt;In a badly run kitchen, the chefs cook the food and just slide it through a hatch to the waiters. The waiters don't know what's in the dish, the chefs don't know what the customers are saying, and when something goes wrong, everyone points fingers.&lt;/p&gt;

&lt;p&gt;In a well run kitchen — like the ones you see at a great restaurant — the chefs and waiters communicate constantly. They know the menu inside out, they get feedback from customers quickly, and they work as one team to give people a great experience.&lt;/p&gt;

&lt;p&gt;DevOps is that well run kitchen, but for software.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real World Example: Amazon
&lt;/h2&gt;

&lt;p&gt;Amazon deploys new code to its website thousands of times per day.&lt;/p&gt;

&lt;p&gt;That means engineers are constantly making small improvements — fixing a bug here, improving the checkout experience there, tweaking a recommendation — and those changes go live almost instantly.&lt;/p&gt;

&lt;p&gt;How? Because Amazon uses DevOps practices. Small changes are automatically tested, automatically checked for problems, and automatically deployed without anyone having to manually press a button.&lt;/p&gt;

&lt;p&gt;In the old way of working, those same changes might have taken weeks to go live, gone through five teams, and required a late night deployment session that everyone dreaded.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Three Big Ideas Behind DevOps
&lt;/h2&gt;

&lt;p&gt;You don't need to memorise these, but it helps to know the thinking behind DevOps.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Work in Small Steps
&lt;/h3&gt;

&lt;p&gt;Instead of building for six months and releasing everything at once (terrifying), DevOps teams release small changes frequently. If something breaks, it's easy to find and fix because the change was tiny.&lt;/p&gt;

&lt;p&gt;Uber does this constantly. Every few weeks, the Uber app gets tiny updates — a new button here, a faster map there. You barely notice, but the team is constantly improving without disrupting your experience.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Automate the Boring Stuff
&lt;/h3&gt;

&lt;p&gt;Testing code manually, deploying to servers manually, checking for errors manually — all of this is slow and humans make mistakes. DevOps teams automate these tasks so they happen instantly and consistently every single time.&lt;/p&gt;

&lt;p&gt;Think of it like a car factory. Cars aren't built by hand anymore — robots do the repetitive work faster and with fewer errors. DevOps applies the same thinking to software.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Get Feedback Fast
&lt;/h3&gt;

&lt;p&gt;When something breaks, DevOps teams know about it within seconds, not days. Monitoring tools watch the system constantly and send alerts the moment something looks wrong.&lt;/p&gt;

&lt;p&gt;Netflix actually has a famous practice where they intentionally break parts of their own system during working hours to make sure their team can fix things quickly. They call it Chaos Engineering. It sounds mad, but it means they're never caught off guard.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Does a DevOps Engineer Actually Do?
&lt;/h2&gt;

&lt;p&gt;A DevOps engineer is the person who builds and maintains the systems that help developers work faster and more safely. They work on things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up automated testing so bugs are caught before they reach users&lt;/li&gt;
&lt;li&gt;Building pipelines that automatically deploy code (we'll cover this in a future article)&lt;/li&gt;
&lt;li&gt;Managing cloud infrastructure on platforms like AWS or Azure&lt;/li&gt;
&lt;li&gt;Monitoring systems and making sure everything is running smoothly&lt;/li&gt;
&lt;li&gt;Writing scripts to automate repetitive tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's one of the most in-demand roles in tech right now, and the skills involved are exactly what this blog is here to help you build.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Should You Care About DevOps?
&lt;/h2&gt;

&lt;p&gt;Whether you're a developer, a system admin, a project manager, or someone just getting into tech — DevOps matters because it's how modern software is built.&lt;/p&gt;

&lt;p&gt;Every major tech company in the world uses DevOps practices. Banks use it to deploy new banking features. Airlines use it to update booking systems. Hospitals use it to improve patient management software. It's not just for Silicon Valley startups — it's everywhere.&lt;/p&gt;

&lt;p&gt;Learning DevOps opens doors. And the best part is, you don't need to know everything at once. We'll take it one byte at a time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Recap
&lt;/h2&gt;

&lt;p&gt;Here's everything we covered in plain English:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DevOps&lt;/strong&gt; = Developers and Operations working together instead of in separate silos&lt;/li&gt;
&lt;li&gt;It solves the old problem of slow, painful, risky software releases&lt;/li&gt;
&lt;li&gt;The core ideas are: &lt;strong&gt;small changes, automation, and fast feedback&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Companies like Amazon, Netflix, and Uber use DevOps to deploy changes thousands of times a day&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;DevOps engineer&lt;/strong&gt; builds the tools and systems that make all of this possible&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In the next article we're going to look at &lt;strong&gt;&lt;a href="https://dev.to/posts/linux-basics-for-devops/"&gt;Linux — The Operating System That Runs the Internet&lt;/a&gt;&lt;/strong&gt; — the OS that powers most of the internet and why every DevOps engineer needs to know the basics.&lt;/p&gt;

&lt;p&gt;It's going to be short, practical, and you'll be typing your first Linux commands before the end of the article. See you there.&lt;/p&gt;




</description>
      <category>devops</category>
      <category>beginners</category>
      <category>cloud</category>
      <category>careerswitch</category>
    </item>
    <item>
      <title>Direct Connect and ExpressRoute: Fixing Asymmetric BGP Routing</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Tue, 01 Sep 2026 01:57:01 +0000</pubDate>
      <link>https://dev.to/agenticdevops/direct-connect-and-expressroute-fixing-asymmetric-bgp-routing-3c67</link>
      <guid>https://dev.to/agenticdevops/direct-connect-and-expressroute-fixing-asymmetric-bgp-routing-3c67</guid>
      <description>&lt;h2&gt;
  
  
  The Story
&lt;/h2&gt;

&lt;p&gt;Back in &lt;a href="https://pipelineandprompts.com/posts/hybrid-cloud-architecture-on-prem-vs-cloud-tradeoffs/" rel="noopener noreferrer"&gt;Article 1&lt;/a&gt;, I said we'd get back to this: once you've decided &lt;em&gt;what&lt;/em&gt; goes in the cloud and &lt;a href="https://pipelineandprompts.com/posts/managed-vs-self-hosted-handing-over-keys/" rel="noopener noreferrer"&gt;&lt;em&gt;who&lt;/em&gt; manages it&lt;/a&gt;, there's a third question that decides whether any of it actually works — how does your data get there?&lt;/p&gt;

&lt;p&gt;A telecom customer I worked with was pushing sustained real-time Kafka streams past 850 Mbps between on-prem and the cloud, with big unpredictable spikes on top, over an AWS Site-to-Site VPN. A single tunnel is rated up to 1.25 Gbps. On paper they had headroom.&lt;/p&gt;

&lt;p&gt;They still hit a wall. It wasn't AWS's fault.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;The wall was the on-prem VPN appliance's CPU — specifically, it couldn't keep up with IPsec encryption for every packet, made worse by two tunnels that weren't splitting traffic evenly. I confirmed this from CloudWatch tunnel metrics and CLI inspection on the appliance itself, not from guessing based on symptoms.&lt;/p&gt;

&lt;p&gt;This is the trap for any platform team running high-throughput streaming over a public-internet VPN: the bottleneck almost never shows up where the bandwidth numbers say it should. Think of a VPN over the public internet like a public road — cheap, open to everyone, fine most days. But there's a single-lane toll booth at the entrance where every car gets checked. On a normal day, minor delay. On a heavy day, that booth &lt;em&gt;is&lt;/em&gt; the road, no matter how wide the highway gets afterward. The toll booth here was IPsec — every packet individually encrypted and decrypted, with real CPU cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Obvious Fixes Didn't Work
&lt;/h2&gt;

&lt;p&gt;Two fixes made sense on paper. Both failed, for different reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling up&lt;/strong&gt; meant an emergency maintenance window to go from 4 vCPUs to 8, plus more RAM. No help — IPsec/IKE encryption for a given tunnel is bound to a single worker thread, and that thread doesn't spread across cores just because more exist. The new cores sat idle. The one thread doing the work stayed pegged at 98%.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaling out&lt;/strong&gt; meant adding two more VPN tunnels, on the theory that more tunnels meant more hash buckets and distributed crypto load. Also failed. ECMP hashes traffic by flow — source/destination IP, source/destination port, protocol — and this Kafka stream was one sustained flow. It didn't matter how many tunnels existed; that flow kept landing on the same path. More lanes feeding the same toll booth. Retries got worse, not better.&lt;/p&gt;

&lt;p&gt;Direct Connect and ExpressRoute are the private road built just for you — no public traffic, no toll booth. They cost more and take time to build, but they behave predictably once they're in place. Most teams start on the public road because it's what's available on day one. This is what happens once your traffic outgrows it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqi68b9qdw30hrpca3bul.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqi68b9qdw30hrpca3bul.png" alt="Architecture Diagram" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram lays out the "two private roads, no shared map" problem: the on-prem VPN appliance with its two IPsec tunnels to AWS; an HAProxy layer using 16 secondary IPs to feed ECMP; Direct Connect (AWS) and ExpressRoute (Azure) running as parallel dedicated circuits, each with dual-location redundancy; independent BGP autonomous systems per cloud with zero shared visibility into each other's routing; and the asymmetric-path failure itself — a request leaving via Direct Connect, its response coming back over ExpressRoute, hitting a stateful firewall that drops it as an unmatched session.&lt;/p&gt;

&lt;p&gt;Direct Connect took six weeks to provision. A single-provider option was ruled out almost immediately — this customer was already deliberately multi-cloud for capacity, cost, and reliability reasons that had nothing to do with this problem. The design became Direct Connect into AWS and ExpressRoute into Azure, each with redundant physical locations, driven by a real contractual requirement: 99.99% uptime.&lt;/p&gt;

&lt;p&gt;Here's where this specific engagement took an unexpected turn. In week two of running both circuits live, a new failure mode showed up: an asymmetric BGP routing loop that broke stateful firewalls. AWS and Azure each run independent BGP autonomous systems with no visibility into each other's routing decisions. Each cloud picked its own "best" path back to the same on-prem address block — unaware the other had picked differently. A request could leave over Direct Connect while the response came back over ExpressRoute, and to a stateful firewall expecting a matched pair, that looks like a session that was never opened. Dropped.&lt;/p&gt;

&lt;p&gt;This is exactly why the failure was subtle: the data plane — the actual Kafka traffic — looked completely healthy right up until the control plane's routing asymmetry collided with a stateful security device.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing It
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; two cloud providers with existing dedicated-circuit relationships (Direct Connect for AWS, ExpressRoute for Azure), BGP peering already established on both circuits, a stateful firewall in the on-prem path, and TLS termination capability at the compute tier rather than just at a central gateway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — buy time while Direct Connect provisions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pulled non-critical workloads off the IPsec tunnels to free capacity for the critical stream&lt;/li&gt;
&lt;li&gt;Deployed local HAProxy bound to 16 separate secondary IPs on the on-prem appliance&lt;/li&gt;
&lt;li&gt;Why it worked: ECMP hashes partly on source/destination IP, so splitting one flow across 16 source IPs made it look like sixteen distinct flows — forcing real distribution across both tunnels instead of one path absorbing everything&lt;/li&gt;
&lt;li&gt;Not off-the-shelf load balancing — HAProxy's only job here was manufacturing enough distinct source IPs to break ECMP's per-flow hash&lt;/li&gt;
&lt;li&gt;Rollback: trivial. Pull HAProxy out of the path, nothing left to unwind&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 2 — fix the asymmetric routing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rejected the easy fix: loosening firewall statefulness to just allow asymmetric traffic — would have weakened a required security posture&lt;/li&gt;
&lt;li&gt;Real fix: BGP community tagging, MED tuning, and AS-path prepending on the Azure side, plus subnet-specific routing, to force one deterministic, symmetric path per address block&lt;/li&gt;
&lt;li&gt;Rollback: reversible by withdrawing the community tags and MED values — but only cleanly if you documented the pre-change route table first. Untangling which of several manual tweaks caused a new asymmetry after the fact is much harder than reverting to a known-good baseline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — get encryption out of the choke point&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rather than reintroduce a central encryption bottleneck — recreating the exact problem that started this story — we moved TLS to the application layer, handled independently by each service instance. Spreading crypto across the compute tier kept continuous pod-to-pod encryption without a single-threaded chokepoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  Security Considerations
&lt;/h2&gt;

&lt;p&gt;A private circuit isn't an encryption exemption. This was telecom data under SOC 2, which requires encryption in transit regardless of whether the path is public or private — "it's on a dedicated circuit" doesn't satisfy that requirement by itself.&lt;/p&gt;

&lt;p&gt;And the obvious fix for the BGP asymmetry — loosening firewall statefulness — was a security regression we explicitly rejected. It would have widened the firewall's tolerance for traffic patterns that look identical to spoofing and session-hijacking attempts. We took the slower, harder BGP-policy route instead of the fast one that degraded posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tradeoffs
&lt;/h2&gt;

&lt;p&gt;Dedicated circuits gave us predictable throughput and latency, and a real path to 99.99% uptime through dual-cloud, dual-location redundancy. In exchange: six weeks of lead time per circuit, and an entirely new failure mode — BGP asymmetry across independent cloud ASNs — that a single-provider VPN never has to deal with.&lt;/p&gt;

&lt;p&gt;Moving TLS to the application layer removed the single-threaded bottleneck entirely, but it cost us centralized visibility. One gateway doing crypto means one place to audit; per-service TLS means per-service certificate management and rotation discipline — more operational surface area, not less.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;p&gt;Design application traffic to use multiple distinguishable connections from the start — one giant flow is always a point of contention, VPN or dedicated circuit alike. And put BGP routing policy templates in place &lt;em&gt;before&lt;/em&gt; the first circuit goes live, not after an outage forces the question. Both failures in this story were, in hindsight, predictable the moment more than one path existed.&lt;/p&gt;

&lt;p&gt;Has any of this actually been tested? Partially. Automatic BGP failover from the primary Direct Connect circuit to the secondary has been proven for real — a production fiber cut triggered it, and it worked. Full failover down to the standby VPN has only run in a controlled drill, never a real outage. Worth naming rather than assuming away.&lt;/p&gt;

&lt;p&gt;And this design has a ceiling. It holds for two clouds and a handful of regions, but it doesn't scale by just repeating the pattern — add enough regions and individually-advertised address blocks and you hit a hard limit on how many routes a provider's edge router will accept. Past that limit it doesn't degrade gracefully; it silently drops excess routes or drops the BGP session entirely, taking every route with it. That blackholes active streams — the exact failure this architecture was built to prevent. The fix at that scale isn't more hand-tuned BGP policy per circuit. It's a managed transit gateway: a centralized hub that handles routing between clouds and regions so no circuit is negotiating its own path in isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Next in Cloud Without the Chaos: &lt;strong&gt;Article 4 — Data Sovereignty in Practice: What "It's in an EU Data Centre" Actually Covers (and Doesn't)&lt;/strong&gt; — going deeper on the compliance angle this article touched on with SOC 2.&lt;/p&gt;

</description>
      <category>directconnect</category>
      <category>expressroute</category>
      <category>bgprouting</category>
      <category>cloudwithoutthechaos</category>
    </item>
    <item>
      <title>Chunked a Document and Found a New Retrieval Problem</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Thu, 27 Aug 2026 12:47:43 +0000</pubDate>
      <link>https://dev.to/agenticdevops/chunked-a-document-and-found-a-new-retrieval-problem-514c</link>
      <guid>https://dev.to/agenticdevops/chunked-a-document-and-found-a-new-retrieval-problem-514c</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; Chunking means splitting a long document into smaller overlapping pieces before embedding each one separately, instead of embedding the whole thing (or truncating it, as &lt;a href="https://dev.to/posts/05-local-embeddings-pipeline/"&gt;Entry 05&lt;/a&gt; did). This fixes content loss — nothing gets silently dropped — but it introduces a structural question that's easy to miss: a document split into 17 chunks now has 17 separate entries competing for a spot in search results, while a short document still only has one. More chunks means more chances to appear in a top-N result, independent of whether that chunk is actually the most relevant thing stored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ran:&lt;/strong&gt; Chunked &lt;a href="https://dev.to/posts/managed-vs-self-hosted-handing-over-keys/"&gt;Managed vs Self-Hosted&lt;/a&gt; (&lt;code&gt;managed-vs-self-hosted-handing-over-keys.md&lt;/code&gt;, split into 17 pieces at 1000 characters with 200-character overlap) and embedded each chunk into the same Chroma collection from Entries &lt;a href="https://dev.to/posts/05-local-embeddings-pipeline/"&gt;05&lt;/a&gt;/&lt;a href="https://dev.to/posts/06-querying-embeddings-store/"&gt;06&lt;/a&gt;. Two real snags on the way:&lt;/p&gt;

&lt;p&gt;First, reconnecting to the collection returned an empty database with only the new chunks in it — no sign of the original 3 entries from Entries 05/06. Turned out &lt;code&gt;PersistentClient(path="./chroma_db")&lt;/code&gt; uses a path relative to wherever Python was launched from, and this session started in a different folder than the earlier ones. A &lt;code&gt;find&lt;/code&gt; across the filesystem turned up three separate &lt;code&gt;chroma_db&lt;/code&gt; folders — the "empty" one was actually a brand-new database created by accident, not data loss. Fixed by returning to the original working directory before reconnecting.&lt;/p&gt;

&lt;p&gt;Second, after fixing that, an early query attempt using &lt;code&gt;collection.query(query_texts=[...])&lt;/code&gt; failed with &lt;code&gt;InvalidArgumentError: Collection expecting embedding with dimension of 768, got 384&lt;/code&gt; — passing raw text instead of a pre-computed embedding makes Chroma fall back to its own default embedding model, which produces a different vector size than &lt;code&gt;nomic-embed-text&lt;/code&gt;. Same lesson as Entry 06: always embed the query with the same model used for the documents.&lt;/p&gt;

&lt;p&gt;With that sorted, chunked the new document and embedded each piece into the correct, 20-entry collection:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;

&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;managed-vs-self-hosted-handing-over-keys.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;chunk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;
&lt;span class="n"&gt;overlap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;
&lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;chunk_size&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;chunk_size&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;overlap&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunks&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-embed-text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;managed-vs-self-hosted-handing-over-keys_chunk&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
        &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# → 20 (3 original entries + 17 new chunks)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ran three queries against it — the same "oc pod status" and "pizza topping" questions from Entry 06, plus a real question about the new document's actual topic. Each question has to be embedded with the same model used for the documents before querying — passing raw text via &lt;code&gt;query_texts&lt;/code&gt; instead triggers Chroma's own default embedding model, which produces a different vector size and fails outright:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;q1_embed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-embed-text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;how do I check pod status with oc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;q1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;q1_embed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;n_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;q2_embed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-embed-text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;what&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the best pizza topping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;q2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;q2_embed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;n_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;q3_embed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-embed-text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What are my options for kubernetes, should I use managed or self-hosted Kubernetes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;q3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;q3_embed&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;n_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Query 1:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;q1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Query 2:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;q2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Query 3:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;q3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;q3&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Top 3 matches&lt;/th&gt;
&lt;th&gt;Distances&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"how do I check pod status with oc"&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;02-oc-cli-mentor...&lt;/code&gt; (correct), then 2 unrelated chunks&lt;/td&gt;
&lt;td&gt;437.72, 450.32, 453.22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"what's the best pizza topping"&lt;/td&gt;
&lt;td&gt;3 unrelated chunks (all from the new doc)&lt;/td&gt;
&lt;td&gt;519.80, 531.01, 531.51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"managed or self-hosted Kubernetes"&lt;/td&gt;
&lt;td&gt;3 correct chunks from the new doc&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;290.34, 314.06, 316.37&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things stand out. The on-topic Kubernetes question is the tightest, cleanest match of the whole series so far — every one of the top 3 results came from the right document, at meaningfully lower distances than anything seen in Entries 05 or 06. Chunking clearly works for making a long document's actual content findable.&lt;/p&gt;

&lt;p&gt;But the oc question shows the tradeoff directly: in Entry 06, its #2 result was the genuinely-related URL entry at distance 499.63. Here, that same document got pushed entirely out of the top 3, replaced by two irrelevant chunks from the 17-chunk document at 450.32 and 453.22 — lower distances not because they're more relevant, but because a 17-chunk document simply has more entries competing for the middle-ranked spots.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Chunking is a real fix for the content-loss problem from Entry 05, and the on-topic result here is the strongest retrieval this series has produced. But it's not a free upgrade — a document with many chunks crowds out equally-relevant single-entry documents just by having more shots at ranking. Production RAG systems typically handle this with per-document result caps or a re-ranking step after initial retrieval; that's the natural next thing to test, rather than assuming more chunks always means better search.&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>rag</category>
      <category>embeddings</category>
      <category>chromadb</category>
    </item>
    <item>
      <title>Queried the Local Embeddings Store for the First Time</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Mon, 24 Aug 2026 14:49:55 +0000</pubDate>
      <link>https://dev.to/agenticdevops/queried-the-local-embeddings-store-for-the-first-time-42pf</link>
      <guid>https://dev.to/agenticdevops/queried-the-local-embeddings-store-for-the-first-time-42pf</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; Building the vector store in &lt;a href="https://dev.to/posts/05-local-embeddings-pipeline/"&gt;Entry 05&lt;/a&gt; was only half the job — the actual point of embeddings is being able to ask a question and get back the &lt;em&gt;right&lt;/em&gt; stored document, not just any document. Chroma does this by comparing distances: turn the question into a vector the same way you turned each document into one, then measure how numerically close the question's vector is to each stored vector. Lower distance means more similar in meaning. The real test isn't whether it returns &lt;em&gt;something&lt;/em&gt; — it always will — it's whether the distance numbers actually mean anything, i.e. whether relevant matches consistently score lower than irrelevant ones.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ran:&lt;/strong&gt; Reconnected to the collection built in Entry 05 (&lt;code&gt;collection.count()&lt;/code&gt; confirmed all 3 entries were still there), then ran two queries through the same embed-then-search pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-embed-text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;how do I check pod status with oc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;n_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ids&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;02-oc-cli-mentor-system-prompt.md&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;posts_03-1b-vs-3b-memory-comparison&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;437.72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;499.63&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bad_q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-embed-text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;what&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s the best pizza topping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bad_results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query_embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;bad_q&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;n_results&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;bad_results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;distances&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mf"&gt;542.33&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;585.48&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Query&lt;/th&gt;
&lt;th&gt;Top match&lt;/th&gt;
&lt;th&gt;Best distance&lt;/th&gt;
&lt;th&gt;Worst distance&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;"how do I check pod status with oc"&lt;/td&gt;
&lt;td&gt;&lt;code&gt;02-oc-cli-mentor-system-prompt.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;437.72&lt;/td&gt;
&lt;td&gt;499.63&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;"what's the best pizza topping"&lt;/td&gt;
&lt;td&gt;(same 2 docs, wrong topic)&lt;/td&gt;
&lt;td&gt;542.33&lt;/td&gt;
&lt;td&gt;585.48&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things worth calling out. First, the on-topic question correctly surfaced the oc-mentor file as the closest match — the system found the right document, not just a document. Second, and more convincing: &lt;strong&gt;every distance for the real question was lower than every distance for the pizza question&lt;/strong&gt; — even the &lt;em&gt;worst&lt;/em&gt; on-topic match (499.63) beat the &lt;em&gt;best&lt;/em&gt; off-topic match (542.33). That's a real, if small, sample showing the distance metric isn't just noise; it's actually tracking relevance.&lt;/p&gt;

&lt;p&gt;One loose thread: the third stored document — the long draft article that got truncated by 57% in Entry 05 — never showed up in either top-2 result, including for queries that might plausibly relate to it. Can't tell yet whether that's because it's genuinely less relevant to these two test questions, or because truncating away more than half its content damaged what got embedded. Worth testing directly once chunking replaces truncation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; The retrieval loop works — real semantic discrimination, not just returning whatever's closest by default. But this was a 3-document store and 2 test questions; that's a proof of concept, not a rigorous eval. The next real fix is the &lt;a href="https://dev.to/posts/07-chunking-retrieval-bias/"&gt;chunking work in Entry 07&lt;/a&gt; — and specifically checking whether it changes how the long truncated document performs in queries like this one.&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>rag</category>
      <category>embeddings</category>
      <category>chromadb</category>
    </item>
    <item>
      <title>Managed Services vs Self-Hosted: When to Hand Over the Keys</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Fri, 21 Aug 2026 20:16:36 +0000</pubDate>
      <link>https://dev.to/agenticdevops/managed-services-vs-self-hosted-when-to-hand-over-the-keys-3cpl</link>
      <guid>https://dev.to/agenticdevops/managed-services-vs-self-hosted-when-to-hand-over-the-keys-3cpl</guid>
      <description>&lt;h1&gt;
  
  
  Managed Services vs Self-Hosted: When to Hand Over the Keys
&lt;/h1&gt;

&lt;p&gt;☁ Cloud Without the Chaos — Article 02&lt;/p&gt;

&lt;p&gt;You've placed the workload in the cloud. That decision is done. Now comes the one nobody gives you a clean framework for: do you run the infrastructure yourself, or do you hand the keys to a managed service?&lt;/p&gt;

&lt;p&gt;I worked with a customer who answered this the hard way — twice. Once on their own platform, and once after they'd already made the switch. Both incidents involved the same underlying failure mode. Neither one turned out the way anyone in the room expected.&lt;/p&gt;

&lt;h2&gt;
  
  
  The warehouse analogy
&lt;/h2&gt;

&lt;p&gt;In supply chain, this decision has a name: &lt;a href="https://dev.to/posts/hybrid-cloud-architecture-on-prem-vs-cloud-tradeoffs/"&gt;make vs. buy&lt;/a&gt;. Do you run your own warehouse, or use a third-party logistics provider (3PL)?&lt;/p&gt;

&lt;p&gt;Running your own warehouse means you control everything — layout, staffing, hours, how fast you respond when a shipment needs to move at 2am. It also means you own every forklift breakdown and every understaffed shift.&lt;/p&gt;

&lt;p&gt;A 3PL means someone else owns the building, the labor, the equipment. You focus on what you sell instead of how it's stored and moved. But you're also dependent on their systems, their SLAs, and their priorities when your shipment competes with someone else's for dock space.&lt;/p&gt;

&lt;p&gt;Cloud infrastructure is the same trade in different clothes. Self-hosted is your own warehouse. Managed platforms are the 3PL. What follows is what that trade actually looked like for one team, in production, with real numbers attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  The renewal that forced the question
&lt;/h2&gt;

&lt;p&gt;The customer had a lean platform team self-managing Kubernetes — patching, logging, monitoring, upgrades, all of it in-house. Their platform renewal was coming up, expensive, and stacked against a team that was already stretched. The maintenance burden that made the renewal conversation painful had a specific shape: every major Kubernetes upgrade meant validating deprecated and removed APIs across their entire application estate, inside a fixed window, before the old control plane version went out of support.&lt;/p&gt;

&lt;p&gt;That window was 30 days. The estate was roughly 1,400 microservices across 120+ namespaces. And on one upgrade cycle, something got missed.&lt;/p&gt;

&lt;h2&gt;
  
  
  The incident that made the case for managed
&lt;/h2&gt;

&lt;p&gt;During a post-upgrade hotfix rollout, a Helm chart still referencing a removed Ingress API version hit the CD pipeline. The deployment failed. So the team did what you're trained to do — they rolled back.&lt;/p&gt;

&lt;p&gt;The rollback failed too, and it failed for a specific, mechanical reason worth understanding rather than waving past. Helm doesn't recompute a prior release from source when you roll back — it retrieves the immutable historical manifest stored in a cluster Secret and re-applies it, which means that manifest still has to pass live API discovery validation on the way back in. Kubernetes 1.22 had removed the &lt;code&gt;v1beta1&lt;/code&gt; Ingress API group entirely. The API server had no way to accept a manifest written against an API version that no longer existed on the cluster. Helm halted before applying anything.&lt;/p&gt;

&lt;p&gt;With the rollback path closed, the Ingress controller hit a state-sync failure. Payments traffic started returning 502s. Engineers resolved it by hand — removing orphaned resources, rewriting the manifests to the current API schema, and re-running the deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forty-five minutes of active, customer-facing outage on payments&lt;/strong&gt;, before service was restored.&lt;/p&gt;

&lt;p&gt;The team built a deprecation-scanning guardrail afterward. It was the right fix for the immediate problem. But it didn't touch the actual bottleneck, which wasn't technical — it was a small team with too many competing priorities trying to own an entire platform lifecycle by hand. That's what pushed the renewal conversation toward evaluating ROSA HCP and EKS as replacements, rather than just renewing what they had.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why ROSA HCP over EKS
&lt;/h2&gt;

&lt;p&gt;The deciding factor wasn't a feature checklist. It was a question of who owns which lifecycle. EKS would have meant the customer still assembling and operating most of the surrounding components themselves. ROSA HCP's integrated stack meant Red Hat owned Day 2 operations on the platform — the control plane, the surrounding components, the patching cadence — while the customer kept ownership of the application layer. For a team whose whole problem was "we can't keep operating all of this ourselves," that division of labor was the actual point — the same &lt;a href="https://dev.to/posts/stopped-managing-kubernetes-start-managing-applications/"&gt;operational-ownership calculus&lt;/a&gt; that plays out anywhere a team decides what to stop managing directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The surprise: managed doesn't mean hands-off
&lt;/h2&gt;

&lt;p&gt;Here's where the assumption cracked. The team expected that ROSA's managed Day 2 operations meant node draining, cordoning, and worker upgrades would happen the way a serverless abstraction behaves — invisibly, without touching running workloads.&lt;/p&gt;

&lt;p&gt;That's not how it works. On ROSA HCP, control plane upgrades are decoupled and handled automatically by Red Hat. Worker node pool upgrades are not automatic in the same way — they require customer initiation, and once initiated, they're still governed by whatever workload configuration is sitting on top of those nodes.&lt;/p&gt;

&lt;p&gt;A critical CVE forced the question. Red Hat's SREs patched the managed control plane within 24 hours of disclosure — the response time this engagement actually observed, not a contractual SLA figure. When the customer then initiated the worker pool upgrade, ROSA's automated drain process ran into a misconfigured PodDisruptionBudget (PDB) on a legacy service. The PDB was blocking pod eviction, so the node drain stalled.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contrast that matters
&lt;/h2&gt;

&lt;p&gt;This is the moment worth sitting with, because it's the clearest evidence for how this trade actually plays out.&lt;/p&gt;

&lt;p&gt;On the self-hosted side, the team discovered their outage the way most self-hosted teams discover outages: customers saw 502s first. Forty-five minutes of active, visible damage before anyone had it under control.&lt;/p&gt;

&lt;p&gt;On ROSA HCP, Red Hat's SRE automation caught the stalled drain and alerted the customer's team directly — before any workload was affected. The Machine Config Operator's drain process retried against the exponential backoff pattern it's designed to use rather than failing outright, so once the customer's application team fixed their own PDB configuration, the rollout resumed and completed on its own. No support ticket. No manual cluster-operator intervention from Red Hat.&lt;/p&gt;

&lt;p&gt;That episode took &lt;strong&gt;2 to 2.5 hours&lt;/strong&gt; from alert to resolution — longer than the payments outage. But customer-facing impact was zero, because the misconfigured PDB had done its one useful job: it prevented running pods from being prematurely terminated while the drain was stuck.&lt;/p&gt;

&lt;p&gt;Managed didn't mean faster. It meant the failure mode moved from "customer-facing outage you discover yourself" to "silent stall you get alerted to before anyone outside notices."&lt;/p&gt;

&lt;h2&gt;
  
  
  The exposure window nobody puts in the vendor deck
&lt;/h2&gt;

&lt;p&gt;The CVE episode had a second layer worth naming plainly, because it's the part every "just move to managed" pitch tends to skip.&lt;/p&gt;

&lt;p&gt;Red Hat's SREs patched the control plane in 24 hours. But because worker upgrades required customer initiation, and the customer held off starting that upgrade until their standard maintenance window — partly out of exactly the fear that then materialized as the PDB stall — the worker nodes running actual application workloads stayed unpatched for roughly &lt;strong&gt;four days&lt;/strong&gt;. The 2.5-hour PDB delay was a small piece of that gap. The bigger piece was hesitation.&lt;/p&gt;

&lt;p&gt;Managed infrastructure narrowed the attack surface. It didn't eliminate the scheduling risk sitting on the customer's side of the line.&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually closes the gap
&lt;/h2&gt;

&lt;p&gt;The fix wasn't anything Red Hat needed to change. It was proactive PodDisruptionBudget hygiene at the manifest level, applied consistently, so that node pool upgrades stop being a feared, manually-delayed event and become a routine background operation the team can trust. Once that trust exists, there's no reason to hold a CVE-driven worker upgrade for a scheduled maintenance window. The team's target shifted from a hesitant four-day window down to an automated 24-hour response following any control-plane security release — closing the exposure gap by removing the fear that created it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this breaks at scale
&lt;/h2&gt;

&lt;p&gt;At 120+ namespaces and 1,400+ microservices, PDB hygiene enforced only in CI/CD isn't enough. Pipeline linting has a blind spot: it can't see PodDisruptionBudgets generated at runtime by Operators, bundled inside third-party Helm charts, or applied directly via &lt;code&gt;kubectl&lt;/code&gt; outside any pipeline at all. A policy that only lives in CI will pass clean while the cluster quietly accumulates exactly the kind of misconfiguration that stalled the worker upgrade in the first place. &lt;a href="https://dev.to/posts/terraform-managed-openshift-state/"&gt;State drift on managed platforms follows the same pattern&lt;/a&gt; — the enforcement point that looked sufficient in isolation turns out to have blind spots once the estate gets large enough.&lt;/p&gt;

&lt;p&gt;The customer's answer was to move enforcement to the cluster's front door — deploying Kyverno as a &lt;code&gt;ValidatingWebhookConfiguration&lt;/code&gt; at the Kubernetes API server itself, so no PDB, regardless of where it came from, gets accepted onto the cluster without passing policy. It's deployed today. It hasn't yet been tested against a live attempted violation in production, so I'd call it the right architecture rather than a proven one — worth watching, not yet worth treating as settled.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway that doesn't show up in vendor documentation
&lt;/h2&gt;

&lt;p&gt;The lesson here isn't "avoid managed services," and it isn't "always choose managed services" either. It's this: handing off infrastructure management doesn't reduce your governance burden. It relocates it, and intensifies it, at the API layer.&lt;/p&gt;

&lt;p&gt;Once a vendor owns everything below the API — control plane, patching, node lifecycle — your job stops being "keep the cluster running." It becomes enforcing strict, API-level tenant policy so that the vendor's automation doesn't hit an invisible wall it has no way to see past. The vendor owns the warehouse. You still own what's on the shelves, and if what's on the shelves is stacked wrong, the automated forklift stops moving no matter how good the warehouse is.&lt;/p&gt;

&lt;h2&gt;
  
  
  A framework, not a rule
&lt;/h2&gt;

&lt;p&gt;Three questions do most of the real work in this decision:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Is the scarce resource time, or is it control?&lt;/strong&gt;&lt;br&gt;
If the team is small and stretched, and the system isn't your competitive differentiator, lean managed. If it's core to what makes your product actually work, the operational knowledge from running it yourself has value beyond uptime.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Can you operate it today, under pressure — not eventually, today?&lt;/strong&gt;&lt;br&gt;
If not, that's not an argument for self-hosting. It's a sign you need training or headcount before this decision is even yours to make cleanly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. When it breaks, whose policy is actually holding it together?&lt;/strong&gt;&lt;br&gt;
Managed services don't remove your governance obligations. They just move the point of failure from "the platform" to "whatever policy your team enforces on top of it." Know which one you're actually testing before you sign the renewal.&lt;/p&gt;

&lt;p&gt;None of these have a universal answer. They have an answer for your team, this quarter, with the staffing and policy discipline you actually have — not the version you're hoping to build eventually.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repo
&lt;/h2&gt;

&lt;p&gt;No companion repository for this article — this is a decision-framework piece about managed vs. self-hosted infrastructure, not an implementation walkthrough. Article 3 in this series returns to concrete implementation, where API-level governance becomes its own architecture decision.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cloud Without the Chaos, Article 03&lt;/strong&gt; picks up where this one leaves off: once you've handed the keys to a managed platform and enforced policy at the API layer, connectivity becomes the next architecture decision — VPNs, Direct Connect, ExpressRoute, and what actually breaks when two environments have to talk to each other reliably.&lt;/p&gt;

</description>
      <category>rosahcp</category>
      <category>openshift</category>
      <category>kubernetes</category>
      <category>managedservices</category>
    </item>
    <item>
      <title>Built My First Local Embeddings Pipeline (Files + a Live URL)</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Fri, 21 Aug 2026 14:48:51 +0000</pubDate>
      <link>https://dev.to/agenticdevops/built-my-first-local-embeddings-pipeline-files-a-live-url-1mik</link>
      <guid>https://dev.to/agenticdevops/built-my-first-local-embeddings-pipeline-files-a-live-url-1mik</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; An embedding model doesn't generate text — it converts text into a list of numbers (a vector) that represents the &lt;em&gt;meaning&lt;/em&gt; of that text. Two pieces of writing about similar topics end up with similar vectors, even if they don't share any exact words, which is what makes semantic search possible: you can find "things that mean something like this," not just "things that contain this exact word." One detail that surprised me going in: the vector is always the same fixed length, no matter how long the original text is — a one-sentence note and a 10-page article both come out as the same-sized list of numbers, like a fingerprint that summarizes something much bigger into a fixed format. RAG (retrieval-augmented generation) is built on this: embed a bunch of documents, embed a question the same way, and find whichever documents are numerically closest to the question's meaning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ran:&lt;/strong&gt; Built this up one command at a time instead of writing the full script upfront — easier to see what each piece actually does before combining them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Pull the embedding model&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ollama pull nomic-embed-text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2 — Test the embedding API directly, before writing any Python&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ollama exposes embeddings over a local HTTP API, so you can sanity-check it works with a plain &lt;code&gt;curl&lt;/code&gt; before involving any code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:11434/api/embeddings &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"model": "nomic-embed-text", "prompt": "hello world"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns a JSON object with a single &lt;code&gt;embedding&lt;/code&gt; field — a list of 768 numbers. That's the whole idea of an embedding in one command: text in, fixed-length list of numbers out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3 — Install the Python pieces&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip3 &lt;span class="nb"&gt;install &lt;/span&gt;ollama chromadb requests beautifulsoup4 &lt;span class="nt"&gt;--break-system-packages&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4 — Embed one local file, interactively&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Rather than run a full script blind, this is small enough to do a few lines at a time in a Python shell (&lt;code&gt;python3&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;02-oc-cli-mentor-system-prompt.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ollama&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;embeddings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nomic-embed-text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;   &lt;span class="c1"&gt;# → 768
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit &lt;code&gt;ModuleNotFoundError: No module named 'ollama'&lt;/code&gt; on the first attempt here — Step 3 hadn't been run yet. A reminder that even a five-step walkthrough has room to skip a step by accident.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5 — Store it in Chroma&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chromadb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;PersistentClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./chroma_db&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;collection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_or_create_collection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;today_i_ran_notes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upsert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;02-oc-cli-mentor-system-prompt.md&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;embeddings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;embedding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
    &lt;span class="n"&gt;documents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;collection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# → 1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 6 — Add a longer file, and hit a real limit&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Repeating Step 4 against a longer draft article failed differently:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ollama._types.ResponseError: the input length exceeds the context length
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;nomic-embed-text&lt;/code&gt; has a 2048-token context window, and the longer draft (13,985 characters) blew past it. Quick fix for now — truncate to a safe length before embedding:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;6000&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not the right long-term fix (more on that below), but enough to keep moving.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 7 — Add a live URL as a source&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pulling in a web page instead of a local file needs one extra step first: fetching the page and stripping out the HTML noise (nav bars, scripts, footers) so only the article text gets embedded.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;bs4&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BeautifulSoup&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://pipelineandprompts.com/posts/03-1b-vs-3b-memory-comparison/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                         &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;soup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;BeautifulSoup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html.parser&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;tag&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;script&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;style&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nav&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;footer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;header&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aside&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;decompose&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;article_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;soup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;separator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strip&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;article_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# → 3359
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That gets fed into the same &lt;code&gt;ollama.embeddings()&lt;/code&gt; call from Step 4.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; Three sources ended up embedded across the two files plus the URL from the steps above:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Characters sent&lt;/th&gt;
&lt;th&gt;Vector dimensions&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;cloud-without-chaos-01.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;local file&lt;/td&gt;
&lt;td&gt;6,000 (truncated from 13,985)&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;02-oc-cli-mentor-system-prompt.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;local file&lt;/td&gt;
&lt;td&gt;2,941&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Entry 03 (live URL)&lt;/td&gt;
&lt;td&gt;web article&lt;/td&gt;
&lt;td&gt;3,359&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things stood out. First, every vector came back at exactly 768 dimensions regardless of source length — confirms the "fixed-size fingerprint" idea from the context section wasn't just theory. Second, and more important: the truncation fix meant &lt;strong&gt;57% of the longest draft's content (7,985 of 13,985 characters) never made it into its embedding at all.&lt;/strong&gt; That's not a rounding error — over half the article is invisible to any future search against that vector. This is exactly why real RAG pipelines chunk long documents into smaller overlapping pieces instead of truncating: chunking keeps everything searchable, truncation just quietly throws away whatever didn't fit.&lt;/p&gt;

&lt;p&gt;The URL fetch also worked cleanly — 3,359 characters extracted from the live page is close to the article's actual body length, suggesting the nav/footer/script stripping in the script did its job without pulling in much boilerplate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; The pipeline works end-to-end — files and live URLs, embedded into a queryable local store — but truncation is a real data-loss bug, not just a technical footnote, once you're working with anything longer than a short note. &lt;a href="https://dev.to/posts/06-querying-embeddings-store/"&gt;Entry 06&lt;/a&gt; queries this store for the first time, and &lt;a href="https://dev.to/posts/07-chunking-retrieval-bias/"&gt;Entry 07&lt;/a&gt; replaces truncation with real chunking. For the production-grade version of this — FastAPI, proper chunking, and a real API — see &lt;a href="https://dev.to/posts/ai-in-the-stack-02-rag-runbooks/"&gt;Build a RAG Pipeline for Internal Runbooks&lt;/a&gt; in the AI in the Stack series.&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>rag</category>
      <category>embeddings</category>
      <category>chromadb</category>
    </item>
    <item>
      <title>Compared Quantization Levels: Q4 vs Q8 vs FP16 on llama3.2:1b</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Wed, 19 Aug 2026 21:44:52 +0000</pubDate>
      <link>https://dev.to/agenticdevops/compared-quantization-levels-q4-vs-q8-vs-fp16-on-llama321b-2bf2</link>
      <guid>https://dev.to/agenticdevops/compared-quantization-levels-q4-vs-q8-vs-fp16-on-llama321b-2bf2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; A model's weights — the numbers it uses to reason — are normally stored at high precision, like FP16 (16-bit floating point). Quantization compresses those numbers down to fewer bits (Q8, Q4, etc.) to shrink the file size and memory footprint, trading some numerical precision for big space savings. It's a similar idea to compressing a high-resolution photo into a smaller JPEG: the file shrinks a lot, some detail is technically lost, but for most practical purposes it's hard to tell the difference.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ran:&lt;/strong&gt; Before pulling anything new, ran &lt;code&gt;ollama show llama3.2:1b&lt;/code&gt; to check what &lt;a href="https://dev.to/posts/01-ollama-local-deploy/"&gt;Entry 01&lt;/a&gt;'s baseline model actually was — and it turned out to already be &lt;strong&gt;Q8_0&lt;/strong&gt;, not an unspecified "default" as I'd assumed back in Entry 01. Worth a correction: Entry 01 and Entry 03's &lt;code&gt;1b&lt;/code&gt; numbers were Q8, not full precision. Pulled two more variants for comparison: &lt;code&gt;llama3.2:1b-instruct-q4_K_M&lt;/code&gt; (an explicit 4-bit quantization) and &lt;code&gt;llama3.2:1b-instruct-fp16&lt;/code&gt; (full, uncompressed precision). Loaded each in turn, sent the same test question, and captured &lt;code&gt;ollama ps&lt;/code&gt; / &lt;code&gt;ps aux&lt;/code&gt; for all three quantization levels.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Q4_K_M&lt;/th&gt;
&lt;th&gt;Q8_0 (Entry 01 baseline)&lt;/th&gt;
&lt;th&gt;FP16&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Download size&lt;/td&gt;
&lt;td&gt;807 MB&lt;/td&gt;
&lt;td&gt;1.3 GB&lt;/td&gt;
&lt;td&gt;2.5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ollama ps&lt;/code&gt; loaded size&lt;/td&gt;
&lt;td&gt;997 MB&lt;/td&gt;
&lt;td&gt;1.5 GB&lt;/td&gt;
&lt;td&gt;2.7 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process RSS&lt;/td&gt;
&lt;td&gt;~0.94 GB&lt;/td&gt;
&lt;td&gt;~1.24 GB&lt;/td&gt;
&lt;td&gt;~2.57 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Memory scales cleanly with bit-width across all three: roughly 0.94 GB → 1.24 GB → 2.57 GB as precision doubles from 4-bit to 8-bit to 16-bit. That's a tidier relationship than I expected going in — quantization level is a solid, predictable lever for memory sizing.&lt;/p&gt;

&lt;p&gt;For quality, I asked all three quantization levels the same real question: "what are good free AI tools that can create simple Google Slides using instructions?" None gave a clean answer. All three produced long lists mixing legitimate tools (Canva) with tools that have nothing to do with making slides — Q4 suggested Midjourney and DALL-E, FP16 added Artbreeder, Deep Dream Generator, and Prisma to the same mix. Full precision didn't fix this: FP16's list was just as padded with irrelevant suggestions as Q4's, and Q4 wasn't noticeably worse.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; Memory scaled cleanly and predictably with quantization level — roughly doubling at each step from Q4 to Q8 to FP16, matching the bit-width math almost exactly. Answer quality told a different story: going all the way to full precision didn't reduce hallucinated suggestions on this open-ended recommendation question, and Q4 wasn't visibly worse than FP16. That's not proof quantization is "safe" in general — this was one soft question, not a rigorous eval — but it's a reminder that the real quality gap probably shows up on harder tasks (math, code, precise instruction-following), not casual recommendation queries. Also worth remembering going forward: check &lt;code&gt;ollama show &amp;lt;model&amp;gt;&lt;/code&gt; before assuming what quantization a "default" pull actually gives you. This corrects assumptions from both &lt;a href="https://dev.to/posts/01-ollama-local-deploy/"&gt;Entry 01&lt;/a&gt; and &lt;a href="https://dev.to/posts/03-1b-vs-3b-memory-comparison/"&gt;Entry 03&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama show llama3.2:1b
&lt;span class="go"&gt;  quantization        Q8_0

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama show llama3.2:1b-instruct-q4_K_M
&lt;span class="go"&gt;  quantization        Q4_K_M

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama ps
&lt;span class="go"&gt;NAME                         ID              SIZE      PROCESSOR    CONTEXT    UNTIL
llama3.2:1b-instruct-fp16    2887c3d03e74    2.7 GB    100% GPU     4096       4 minutes from now

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;ollama
&lt;span class="go"&gt;flyers  10763  0.3  15.7  438101456  2630128  ??  S  llama-server --model ... -c 4096
flyers   2256  0.0   0.3  436826160    56448  ??  S  ollama serve

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama ps
&lt;span class="go"&gt;NAME                           ID              SIZE      PROCESSOR    CONTEXT    UNTIL
llama3.2:1b-instruct-q4_K_M    22bc6b92eb01    997 MB    100% GPU     4096       4 minutes from now

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;ollama
&lt;span class="go"&gt;flyers  10786  0.2  5.7  436484688  963808  ??  S  llama-server --model ... -c 4096
flyers   2256  0.0  0.4  436826160   60400  ??  S  ollama serve
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>ollama</category>
      <category>llm</category>
      <category>todayiran</category>
      <category>quantization</category>
    </item>
    <item>
      <title>Agentic AI Infrastructure: What It Takes to Do It Safely</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Wed, 19 Aug 2026 03:00:06 +0000</pubDate>
      <link>https://dev.to/agenticdevops/agentic-ai-infrastructure-what-it-takes-to-do-it-safely-2bio</link>
      <guid>https://dev.to/agenticdevops/agentic-ai-infrastructure-what-it-takes-to-do-it-safely-2bio</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;⚡ Byte Size Summary&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;See why we shipped an OpenShift diagnostic MCP server as &lt;strong&gt;read-only by design&lt;/strong&gt;, and the RBAC wall that made write access harder than it looks&lt;/li&gt;
&lt;li&gt;Walk through a real failed remediation test where an agent recommended a correct-looking fix built on stale, deprecated config — and what that failure mode actually is&lt;/li&gt;
&lt;li&gt;Get the maturity-gated approval architecture we designed for write access — and why it's still sitting on paper, not in production&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Story
&lt;/h2&gt;

&lt;p&gt;In &lt;a href="https://dev.to/posts/ai-in-the-stack-06-n8n-workflows/"&gt;Article 06&lt;/a&gt; we wired an n8n workflow to MCP and RAG for automated incident triage. That article ended with a question: what happens when the agent gets a longer leash?&lt;/p&gt;

&lt;p&gt;We built &lt;code&gt;mcp-sre-tools&lt;/code&gt; — an MCP server that exposes OpenShift and Kubernetes diagnostics to an LLM, wired into Claude Desktop and n8n, covering ARO, ROSA HCP, OSD-GCP, and generic clusters. Nine diagnostic tools: &lt;code&gt;get_cluster_health&lt;/code&gt;, &lt;code&gt;diagnose_crashloop&lt;/code&gt;, &lt;code&gt;get_failing_pods&lt;/code&gt;, and others in that family. &lt;code&gt;READ_ONLY_MODE&lt;/code&gt; is on by default, and there are no write tools in the codebase at all. That part shipped clean.&lt;/p&gt;

&lt;p&gt;The friction started when we scoped what came next: a remediation mode, where the agent wouldn't just diagnose a broken deployment — it would patch it.&lt;/p&gt;

&lt;p&gt;That's where the story stopped being a build story and became an organizational one.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Platform engineers and developers landed on opposite sides of the same question almost immediately, and for reasons that turned out to be more substantial than the usual risk-aversion reflex.&lt;/p&gt;

&lt;p&gt;Developers were comfortable trusting agent-proposed changes roughly the way they'd trust a colleague's pull request — read the diff, sanity-check it, merge it. Platform engineers pushed back hard, and their objection wasn't reflexive. It was specific: a PR from a colleague comes with inspectable reasoning. You can ask them why. An LLM's proposed patch doesn't carry that same trail — the "why" is buried in a forward pass, not a code review comment.&lt;/p&gt;

&lt;p&gt;Business stakeholders, meanwhile, were worried about something simpler and more immediate: an autonomous agent breaking a critical application in production.&lt;/p&gt;

&lt;p&gt;Three legitimate concerns, three different vocabularies for the same underlying question — how much do we trust a system whose reasoning we can't fully inspect, applied to infrastructure we can't afford to break?&lt;/p&gt;




&lt;h2&gt;
  
  
  Why RBAC alone doesn't solve it
&lt;/h2&gt;

&lt;p&gt;The instinct is to reach for RBAC and call it solved. Scope the agent's service account to a namespace, give it patch permissions on Deployments and nothing else, and let it operate inside a fence.&lt;/p&gt;

&lt;p&gt;That fence has a hole in it. Meaningful remediation almost always eventually needs to touch Secrets or environment variables — a misconfigured database connection string, an expired credential reference, a missing env var causing a crash loop. The moment your remediation scope includes Secrets, "namespace-scoped RBAC" stops being clean sandboxing and starts being a much bigger trust surface than the phrase implies.&lt;/p&gt;

&lt;p&gt;We didn't have a way around that with RBAC alone. So we fell back to a narrower, honest justification for read-only: even without write access, a diagnostic agent cuts human mean-time-to-resolution. It's a smaller value proposition than full self-healing, but it's a real one — and it's the one we could actually defend without hand-waving.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Diagram 1 — as built:&lt;/strong&gt; the shipped, read-only MCP architecture.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fibalwhx2wgmc2b5pnkur.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fibalwhx2wgmc2b5pnkur.png" alt="As-built read-only architecture"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The controls we designed the server to work with — note the repo intentionally ships without a default &lt;code&gt;rbac.yaml&lt;/code&gt;, to stay adaptable across cluster types and org policies. Deployment teams are expected to write their own scoped &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/rbac/" rel="noopener noreferrer"&gt;&lt;code&gt;ClusterRole&lt;/code&gt;/&lt;code&gt;RoleBinding&lt;/code&gt;&lt;/a&gt; tailored to their access model; the sample below shows the shape we recommend, not a default that ships:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Namespace-scoped RBAC (recommended, not shipped)&lt;/strong&gt; — bind the MCP server's service account to &lt;code&gt;Role&lt;/code&gt;/&lt;code&gt;RoleBinding&lt;/code&gt; resources scoped per-namespace, not a cluster-wide &lt;code&gt;ClusterRole&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service-account-based access&lt;/strong&gt; — no static kubeconfig or personal credentials in the agent's execution path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NetworkPolicy egress/ingress restriction&lt;/strong&gt; — the MCP server's pod network is fenced to only the cluster API and the LLM endpoint it needs to reach&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Logging and observability as non-functional requirements&lt;/strong&gt; — every tool call is logged, not bolted on after the fact
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Recommended shape, not a shipped default — deployment teams write their own&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Role&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mcp-sre-tools-reader&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;target-namespace&amp;gt;&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pods"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;events"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apps"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployments"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;replicasets"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Diagram 2 — proposed, never implemented:&lt;/strong&gt; the maturity-gated write-access flow.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;This is a design artifact, not a shipped system.&lt;/strong&gt; The kill switch and rollback automation shown below were never built.&lt;br&gt;
&lt;/p&gt;


&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  ┌─────────────────────-┐
  │   Anomaly detected   │
  └──────────┬───────────┘
             │
             ▼
  ┌─────────────────────────────-┐
  │  Agent proposes RBAC-scoped  │
  │      remediation action      │
  └──────────┬───────────────────┘
             │
             ▼
  ┌─────────────────────────────-┐
  │      Risk-based routing      │
  │  low risk        high risk   │
  └──────┬───────────────┬───────┘
         │               │
         ▼               ▼
  ┌─────────────┐ ┌─────────────────────-┐
  │ Light review│ │  Human approval gate │
  └──────┬──────┘ └──────────┬───────────┘
         │                   │
         └─────────┬─────────┘
                   |
                   ▼
        ┌───────────────────────-┐
        │   Kill-switch check    │  ◄── [NEVER BUILT]
        └───────────┬────────────┘
                    │  pass
                    ▼
        ┌───────────────────────-┐
        │  Execute + full audit  │
        │        record          │
        └───────────┬────────────┘
                    │
                    ▼
        ┌───────────────────────-┐
        │ Pre-captured rollback  │  ◄── [NEVER BUILT]
        │   plan (ready before   │
        │      execution)        │
        └───────────┬────────────┘
                    │
                    ▼
        ┌───────────────────────┐
        │    Outcome logged     │
        └───────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The flow we designed, on paper, for a future write-capable mode:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Anomaly detected&lt;/li&gt;
&lt;li&gt;Agent proposes an RBAC-scoped remediation action&lt;/li&gt;
&lt;li&gt;Risk-based routing — low-risk actions get lighter review, high-risk actions route to human approval&lt;/li&gt;
&lt;li&gt;Kill-switch check before execution&lt;/li&gt;
&lt;li&gt;Execution, with a full audit record&lt;/li&gt;
&lt;li&gt;Pre-captured rollback plan available before the action runs, not written after&lt;/li&gt;
&lt;li&gt;Outcome logged&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Two components in that list were never built: the kill switch and the rollback automation. They exist as a design, not as shipped capability. Worth saying plainly, because it's the honest state of the project, not a gap we're hiding.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works: Step by Step
&lt;/h2&gt;

&lt;p&gt;For the part that &lt;em&gt;is&lt;/em&gt; live — the read-only diagnostic path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An alert or a manual query triggers the agent via Claude Desktop or an n8n workflow&lt;/li&gt;
&lt;li&gt;The agent calls one or more of the nine MCP tools (e.g., &lt;code&gt;diagnose_crashloop&lt;/code&gt;) against the target cluster&lt;/li&gt;
&lt;li&gt;The MCP server's service account, scoped by namespace RBAC, executes the read-only API calls&lt;/li&gt;
&lt;li&gt;Results return to the LLM, which synthesizes a diagnosis&lt;/li&gt;
&lt;li&gt;A human reads the diagnosis and decides what to do next — the agent stops there&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For the part that stayed on paper — the proposed remediation path — see the seven-step flow above. It never advanced past step 2 in production; steps 3 through 7 are design artifacts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security and Operational Considerations
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;RBAC and least privilege.&lt;/strong&gt; The repo ships without a default &lt;code&gt;rbac.yaml&lt;/code&gt; by design, to stay adaptable across cluster types — deployment teams write their own scoped &lt;code&gt;ClusterRole&lt;/code&gt;/&lt;code&gt;RoleBinding&lt;/code&gt; for their environment. That's also where the write-mode proposal broke down: remediation that needs to touch Secrets can't stay inside a tidy read-only-style RBAC boundary no matter who authors it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Secrets exposure.&lt;/strong&gt; The live tool set never reads or writes Secret contents. The unbuilt remediation mode is exactly where that boundary would have been tested, and wasn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blast radius.&lt;/strong&gt; Contained by design in the shipped version — nine read-only tools can't mutate cluster state, full stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rollback strategy.&lt;/strong&gt; For the live tools: not applicable, nothing is mutated. For the proposed write mode: rollback was designed as a &lt;em&gt;pre-captured&lt;/em&gt; plan, generated before execution rather than reconstructed after a failure — but this was never implemented, and the one real recovery event we had (below) had to be handled manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auditability.&lt;/strong&gt; Every diagnostic tool call is logged. The proposed write-mode flow adds a full audit record as one of its seven stages — again, unbuilt.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Breaks at Scale
&lt;/h2&gt;

&lt;p&gt;We ran a private test — outside the public repo — with write/patch/upgrade-capable variants of the agent, specifically to see whether the self-healing story held up. It didn't, and the way it failed is the most important finding in this whole project.&lt;/p&gt;

&lt;p&gt;The agent correctly diagnosed a real problem. Then it recommended a fix built on fluentd-era OpenShift Logging configuration — the collector layer — against a cluster already running the current Vector-and-Loki stack. The recommendation looked plausible. It targeted a collector and CRD shape that the cluster had already moved past.&lt;/p&gt;

&lt;p&gt;At close to the same time, in the same test, the agent made a second, unrelated bad call: a recommendation to change the cluster's node-level hardware settings via the worker &lt;code&gt;MachineSet&lt;/code&gt;. That recommendation, applied, provisioned the wrong instance type.&lt;/p&gt;

&lt;p&gt;Two independent, unrelated bad recommendations landing at the same time — one on the logging stack, one on node hardware — made it look, in the moment, like a single cascading failure. It wasn't. Untangling that during recovery took longer than fixing either problem alone would have, precisely because the two failures got conflated. Recovery meant two separate fixes, not one clean GitOps revert: an updated &lt;code&gt;ClusterLogging&lt;/code&gt; CR to throttle ingress at the collector layer and redirect output pipelines directly to the Loki backend, and a reconciliation of the worker &lt;code&gt;MachineSet&lt;/code&gt; back to its approved &lt;code&gt;providerSpec&lt;/code&gt; baseline — scaling down the misconfigured nodes and letting the Machine Operator provision replacements.&lt;/p&gt;

&lt;p&gt;The label that matters here isn't "hallucination" in the dramatic sense people usually mean. The logging recommendation wasn't nonsense — it was &lt;strong&gt;training-data staleness masquerading as competence&lt;/strong&gt;: correct-sounding reasoning built on a collector and config shape the cluster had already moved past. That's a more insidious failure mode than the black-box framing usually implies, because the output looks exactly as confident whether the underlying knowledge is current or stale.&lt;/p&gt;

&lt;p&gt;The instance-type recommendation was a different kind of mistake — not stale knowledge, just a bad call. What made the incident harder to diagnose wasn't either failure alone, but the two landing together and looking, briefly, like one problem instead of two.&lt;/p&gt;

&lt;p&gt;Scaling this up doesn't just mean more permissions — though it does mean that, and the platform team's original objection was concrete and correct on that point alone. It also means the bottleneck moves. Once you gate every write action behind human approval, the approval step itself becomes the constraint, and approval fatigue doesn't scale linearly with the number of things an agent proposes.&lt;/p&gt;




&lt;h2&gt;
  
  
  GitHub Repo
&lt;/h2&gt;

&lt;p&gt;Full implementation: &lt;a href="https://github.com/agentic-devops/mcp-sre-tools" rel="noopener noreferrer"&gt;mcp-sre-tools&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;The open question this leaves for the series: what does a &lt;em&gt;safe&lt;/em&gt; path to write access actually look like, given that the blocker wasn't RBAC mechanics but the reliability of the reasoning behind each proposed action? That's worth its own piece.&lt;/p&gt;

&lt;p&gt;Browse the full &lt;a href="https://dev.to/series/ai-in-the-stack/"&gt;AI in the Stack series&lt;/a&gt; to see how the components — from &lt;a href="https://dev.to/posts/ai-tooling-openshift-evaluation-framework/"&gt;evaluation framework&lt;/a&gt; through &lt;a href="https://dev.to/posts/ai-in-the-stack-02-rag-runbooks/"&gt;RAG&lt;/a&gt;, &lt;a href="https://dev.to/posts/mcp-server-architecture-platform-engineering-kubernetes/"&gt;MCP&lt;/a&gt;, &lt;a href="https://dev.to/posts/prompt-versioning-ci-openshift/"&gt;prompt governance&lt;/a&gt;, &lt;a href="https://dev.to/posts/swapping-llm-providers-without-rewriting-stack/"&gt;provider abstraction&lt;/a&gt;, and &lt;a href="https://dev.to/posts/ai-in-the-stack-06-n8n-workflows/"&gt;n8n orchestration&lt;/a&gt; — connect.&lt;/p&gt;

</description>
      <category>openshift</category>
      <category>mcp</category>
      <category>aiagents</category>
      <category>platformengineering</category>
    </item>
    <item>
      <title>Compared llama3.2:1b vs llama3.2:3b Memory Footprint</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Fri, 14 Aug 2026 17:57:01 +0000</pubDate>
      <link>https://dev.to/agenticdevops/compared-llama321b-vs-llama323b-memory-footprint-34ki</link>
      <guid>https://dev.to/agenticdevops/compared-llama321b-vs-llama323b-memory-footprint-34ki</guid>
      <description>&lt;p&gt;&lt;strong&gt;Context:&lt;/strong&gt; The number in a model name like &lt;code&gt;1b&lt;/code&gt; or &lt;code&gt;3b&lt;/code&gt; refers to parameters — roughly, the tunable values inside the model that encode what it's learned. More parameters generally means better reasoning and more nuanced answers, at the cost of more memory and slower responses. One thing that trips people up early with Ollama: typing &lt;code&gt;ollama run &amp;lt;model&amp;gt;&lt;/code&gt; drops you into an interactive chat session (marked by the &lt;code&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/code&gt; prompt), which is a different context from your regular shell. Commands like &lt;code&gt;ollama ps&lt;/code&gt; only work back in a normal terminal prompt — typed inside the chat session, they get sent to the model as a question instead of running as a command.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ran:&lt;/strong&gt; Pulled &lt;code&gt;llama3.2:3b&lt;/code&gt; (the 3-billion-parameter sibling of Entry 01's &lt;code&gt;1b&lt;/code&gt; model), loaded it into memory, and captured the same &lt;code&gt;ollama ps&lt;/code&gt; / &lt;code&gt;ps aux | grep ollama&lt;/code&gt; numbers for a direct comparison. Along the way, typed &lt;code&gt;ollama ps&lt;/code&gt; inside the chat session by mistake — got a confused response from the model instead of the process table, a good real-world example of the shell-vs-chat distinction above. Then asked the same test question from &lt;a href="https://dev.to/posts/02-oc-cli-mentor-system-prompt/"&gt;Entry 02&lt;/a&gt; ("how do I check the status of pods in my namespace?") — this time against the plain &lt;code&gt;llama3.2:3b&lt;/code&gt; model, not the constrained &lt;code&gt;oc-mentor&lt;/code&gt; build from Entry 02.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;
&lt;code&gt;1b&lt;/code&gt; (Entry 01)&lt;/th&gt;
&lt;th&gt;&lt;code&gt;3b&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ollama ps&lt;/code&gt; size&lt;/td&gt;
&lt;td&gt;1.5 GB&lt;/td&gt;
&lt;td&gt;2.5 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Process RSS&lt;/td&gt;
&lt;td&gt;~1.24 GB&lt;/td&gt;
&lt;td&gt;~2.47 GB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Parameters&lt;/td&gt;
&lt;td&gt;1B&lt;/td&gt;
&lt;td&gt;3B&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Tripling the parameter count roughly doubled the memory footprint — not a 1:1 scaling, which is worth remembering when estimating resource requests for larger models.&lt;/p&gt;

&lt;p&gt;On the question test: since this run used the plain &lt;code&gt;3b&lt;/code&gt; model rather than the &lt;code&gt;oc-mentor&lt;/code&gt; Modelfile from Entry 02, the answer came back as a verbose, multi-option explanation using &lt;code&gt;kubectl&lt;/code&gt; — not &lt;code&gt;oc&lt;/code&gt;, and not the single-command format Entry 02 enforced. That's not a knock on the bigger model; it's a reminder that the constrained, single-command behavior from Entry 02 came from the system prompt, not from model size. A bigger base model without that constraint just reverts to its default training bias (which, unsurprisingly, leans &lt;code&gt;kubectl&lt;/code&gt; over &lt;code&gt;oc&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway:&lt;/strong&gt; More parameters bought roughly 2x memory for 3x the parameter count — a useful data point for future sizing — but it didn't buy domain-specific behavior on its own. Getting oc-only, single-command answers still requires the system prompt from Entry 02, regardless of model size.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ollama ps
&lt;span class="go"&gt;NAME           ID              SIZE      PROCESSOR    CONTEXT    UNTIL
llama3.2:3b    a80c4f17acd5    2.5 GB    100% GPU     4096       4 minutes from now

&lt;/span&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;ps aux | &lt;span class="nb"&gt;grep &lt;/span&gt;ollama
&lt;span class="go"&gt;flyers  94626  0.2  15.1  438021552  2531232  ??  S  llama-server --model ... -c 4096
flyers   1905  0.0   0.4  436904528    60864  ??  S  ollama serve
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;em&gt;Correction (Aug 19, 2026): the &lt;code&gt;1b&lt;/code&gt; baseline referenced from Entry 01 is Q8_0 quantization, not an unspecified default — see &lt;a href="https://pipelineandprompts.com/posts/04-quantization-q4-q8-fp16/" rel="noopener noreferrer"&gt;Entry 04&lt;/a&gt; for the full breakdown across quantization levels.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ollama</category>
      <category>llm</category>
      <category>todayiran</category>
      <category>modelcomparison</category>
    </item>
    <item>
      <title>The Hybrid Cloud Isn't a Destination. It's a Set of Tradeoffs.</title>
      <dc:creator>Nerav Doshi</dc:creator>
      <pubDate>Wed, 12 Aug 2026 19:09:17 +0000</pubDate>
      <link>https://dev.to/agenticdevops/the-hybrid-cloud-isnt-a-destination-its-a-set-of-tradeoffs-dkj</link>
      <guid>https://dev.to/agenticdevops/the-hybrid-cloud-isnt-a-destination-its-a-set-of-tradeoffs-dkj</guid>
      <description>&lt;p&gt;&lt;em&gt;☁ Cloud Without the Chaos — Article 01 · ⏱ 10 min read&lt;/em&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;☁ Byte Size Summary&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hybrid cloud isn't a strategy — it's the outcome of real architectural decisions&lt;/li&gt;
&lt;li&gt;Most teams default to cloud or on-prem for the wrong reasons (inertia, not intent)&lt;/li&gt;
&lt;li&gt;Five dimensions drive every defensible placement decision: latency, data sovereignty, cost profile, operational ownership, and blast radius&lt;/li&gt;
&lt;li&gt;A reusable decision framework for evaluating any workload — run it before the migration starts, not after&lt;/li&gt;
&lt;li&gt;Network design thinking from logistics applies directly to hybrid cloud architecture — and most platform engineers have never seen it framed this way&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;




&lt;p&gt;I spent a few years working in supply chain and logistics before I ended up in cloud infrastructure. One of the first things you learn in that world is that the network is never fully yours.&lt;/p&gt;

&lt;p&gt;You've got your own warehouses. You've got third-party fulfilment centres you use when demand spikes. You've got last-mile carriers you don't control at all. The goods flow across all of it depending on cost, speed, capacity, and — critically — what you're willing to risk having disrupted.&lt;/p&gt;

&lt;p&gt;Nobody in logistics calls this a "hybrid warehouse strategy." They just call it running a network. &lt;strong&gt;The goal isn't ideological purity about who owns the infrastructure. The goal is getting the right things where they need to be, reliably, at a cost that makes sense.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When I moved into cloud and infrastructure, this framing was almost entirely absent. Teams were either "cloud first" or "on-prem forever" — both as fixed identities, not deliberate decisions. When hybrid cloud entered the picture, it was less of a strategy and more of a situation that accumulated over time.&lt;/p&gt;

&lt;p&gt;This series is about bringing some rigour back to those decisions. Not the marketing version of hybrid cloud — the practical one.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "Hybrid Cloud" Actually Means
&lt;/h2&gt;

&lt;p&gt;Strip the vendor positioning out first.&lt;/p&gt;

&lt;p&gt;Hybrid cloud means running workloads across at least two environments — typically some combination of on-premises infrastructure and one or more public cloud providers — with some degree of orchestration or connectivity between them. No requirement for a unified control plane, a specific vendor stack, or a particular ratio of workloads on each side.&lt;/p&gt;

&lt;p&gt;In practice, most enterprise organisations are already hybrid whether they planned it or not. Legacy systems that can't be migrated sit in the data centre. New workloads spin up in AWS or Azure. An OpenShift cluster runs on-prem for regulated workloads. A Kubernetes service runs in Google Kubernetes Engine (GKE) for the developer-facing platform. The network connects them, imperfectly, and the operations team holds it all together with documentation, duct tape, and institutional memory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The question isn't whether you're hybrid. It's whether you're hybrid on purpose.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This article focuses on the strategic decision layer — where workloads should live and why. The technical implementation of connectivity (VPNs, Direct Connect, ExpressRoute) comes later in the series.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Most Hybrid Architectures Are Accidents
&lt;/h2&gt;

&lt;p&gt;Most hybrid architectures evolve the same way.&lt;/p&gt;

&lt;p&gt;A team starts running everything on-prem because that's what they know and what the business already owns. At some point there's a project that needs to move fast, or a new team that doesn't want to wait for physical provisioning, and something lands in a public cloud. Then another thing. Then three more things during a peak traffic event that nobody had capacity for on-prem. Now you're hybrid.&lt;/p&gt;

&lt;p&gt;Or the reverse: a cloud migration programme moves a significant portion of the estate to public cloud. But some workloads — the database with the compliance requirements, the legacy system that can't be containerised, the one application that can't tolerate a 100ms latency spike — stay behind. Now you're hybrid.&lt;/p&gt;

&lt;p&gt;Neither is necessarily wrong. But both share a problem: &lt;strong&gt;the placement decisions were made reactively, and nobody wrote down the reasoning.&lt;/strong&gt; Two years later, a new engineer reverse-engineers why a workload is where it is, and the answer is "history."&lt;/p&gt;

&lt;p&gt;That's a real cost. It makes it harder to have intelligent conversations about what should move, what should stay, and what the actual cost model is.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Five Dimensions That Actually Matter
&lt;/h2&gt;

&lt;p&gt;After working across on-prem and cloud environments — and spending years in supply chain thinking about network design — five dimensions consistently drive the right placement decision. Answer these clearly and you'll reach a defensible architectural position.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Latency tolerance
&lt;/h3&gt;

&lt;p&gt;How sensitive is this workload to network round-trip time? A real-time manufacturing control system talking to floor sensors has hard latency requirements that cloud can't reliably satisfy. A reporting pipeline that runs overnight doesn't. This is easy to underestimate — especially for workloads that look like batch jobs but have real-time dependencies buried inside them.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Data sovereignty and compliance
&lt;/h3&gt;

&lt;p&gt;Where does the data have to live? Regulated data in healthcare, financial services, or government frequently has hard requirements about physical location, access controls, and audit trails. Public cloud providers have made significant progress with regional isolation and compliance certifications — but not every jurisdiction accepts "it's in an EU data centre" as sufficient. Know the actual requirements before you architect.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Cost profile and demand shape
&lt;/h3&gt;

&lt;p&gt;Cloud is cheaper at the margins — burst capacity, unpredictable demand, short-lived environments. On-prem is cheaper at steady state — predictable, high-utilisation workloads where you'd be running equivalent cloud compute continuously. Applying cloud economics to steady-state workloads, or on-prem economics to spiky ones, is the most common cost mistake in hybrid architecture. The shape of demand matters as much as the raw compute cost.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Operational ownership appetite
&lt;/h3&gt;

&lt;p&gt;Cloud shifts operational responsibility to the provider for the infrastructure layer, but it doesn't eliminate operational work — it changes it. You're still responsible for your application, your data, your network configuration, your identity model. A managed Kubernetes service in the cloud may be cheaper in operational time than a self-managed OpenShift cluster, even if the compute costs are higher. Or it may not be, depending on your team. Be honest about this.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Blast radius and failure domain
&lt;/h3&gt;

&lt;p&gt;If this workload fails, or the environment it runs in fails, what's the impact? Placing a critical system on a single public cloud introduces a blast radius tied to that provider's regional availability. Placing it on-prem introduces a different blast radius — your data centre's power, cooling, and network uplinks. Neither is inherently safer. The question is which failure mode you have more control over, more visibility into, and more organisational capacity to recover from.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Decision Framework You Can Actually Use
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LATENCY&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Sub-5ms requirements, or direct integration with on-prem systems?&lt;/td&gt;
&lt;td&gt;→ On-prem or edge. Everything else is negotiable.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SOVEREIGNTY&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hard data residency requirement your cloud provider can't satisfy with certifications?&lt;/td&gt;
&lt;td&gt;→ On-prem or private cloud. If they can satisfy it, document that explicitly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DEMAND SHAPE&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Consistent, predictable utilisation above ~60%?&lt;/td&gt;
&lt;td&gt;→ On-prem likely wins on cost. Spiky, seasonal, or unpredictable? → Cloud wins.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OPERATIONS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Does your team have the skills and bandwidth to manage the infrastructure layer?&lt;/td&gt;
&lt;td&gt;→ On-prem or self-managed. Would a managed service free up meaningful capacity? → Cloud.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;BLAST RADIUS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Which failure mode is more recoverable — cloud regional outage, or on-prem infrastructure failure?&lt;/td&gt;
&lt;td&gt;→ Architect toward the one you can recover from faster.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Run any workload through these five questions. If the answers point consistently in one direction, the decision is clear. If they conflict — and they often do — you're having an explicit architectural conversation rather than an implicit one. That's already progress.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Placement Decision Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Workload type&lt;/th&gt;
&lt;th&gt;Typical best fit&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Real-time control systems&lt;/td&gt;
&lt;td&gt;On-prem / edge&lt;/td&gt;
&lt;td&gt;Latency and reliability requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regulated data stores (PII, PCI, PHI)&lt;/td&gt;
&lt;td&gt;On-prem or certified cloud&lt;/td&gt;
&lt;td&gt;Sovereignty — verify per jurisdiction&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dev/test environments&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Short-lived, variable, low blast radius&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Burst compute (ML training, batch jobs)&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Elastic capacity on demand&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internal developer platforms&lt;/td&gt;
&lt;td&gt;On-prem or hybrid&lt;/td&gt;
&lt;td&gt;Integration with internal systems, security posture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Customer-facing APIs&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Global distribution, managed scaling, CDN integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Legacy monoliths (can't containerise)&lt;/td&gt;
&lt;td&gt;On-prem&lt;/td&gt;
&lt;td&gt;Migration cost exceeds benefit — for now&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disaster recovery / backup&lt;/td&gt;
&lt;td&gt;Cloud&lt;/td&gt;
&lt;td&gt;Offsite resilience, pay-per-use when idle&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  What the Supply Chain World Understood First
&lt;/h2&gt;

&lt;p&gt;In logistics, there's a discipline called &lt;strong&gt;network design&lt;/strong&gt; — deciding where to place inventory, distribution centres, and transport capacity to optimise for cost, speed, and resilience across a network. It's not about owning everything. It's not about outsourcing everything. It's about understanding the specific characteristics of each node and flow, and making placement decisions accordingly.&lt;/p&gt;

&lt;p&gt;The most important lesson from that world: &lt;strong&gt;over-centralising creates fragility, and over-distributing creates complexity that erodes the resilience you were trying to build.&lt;/strong&gt; The answer is never all-in on one model. It's a considered network, with explicit reasoning behind each decision.&lt;/p&gt;

&lt;p&gt;Hybrid cloud is the same problem. The data centre is your warehouse. The cloud is your third-party fulfilment network. Some workloads belong in your warehouse because you need control, compliance, or latency. Others belong in the fulfilment network because you need elasticity and don't want to own the capacity. The failure mode is treating this as a binary choice rather than a network design problem.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 From the field:&lt;/strong&gt; Before any migration or placement decision, create a simple workload inventory: what it does, what it talks to, what its latency and compliance requirements are, and what it currently costs to run. Most teams don't have this and make significant architectural decisions without it. A rough spreadsheet surfaces conversations that would otherwise never happen.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The Honest Tradeoffs
&lt;/h2&gt;

&lt;p&gt;There's no version of hybrid cloud that's free of complexity. Running across two environments means two sets of networking concerns, two operational models, two security boundaries, and often two teams with different toolsets. That's real cost, and it's worth naming before you commit.&lt;/p&gt;

&lt;p&gt;The question isn't whether hybrid cloud creates complexity — it does. The question is whether the complexity is worth it relative to the alternative. For most enterprise organisations with a heterogeneous workload mix and real compliance requirements, the answer is yes. But "yes" should come from an analysis, not from a vendor briefing or a conference keynote.&lt;/p&gt;

&lt;p&gt;The teams that get this right treat placement as a first-class architectural concern — documented, revisited periodically, and tied to actual cost and operational data. Not a one-time migration project. Not a fixed philosophical stance. A living decision.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Hybrid cloud isn't a destination&lt;/strong&gt; — it's a network of deliberate placement decisions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Most hybrid architectures are accidents&lt;/strong&gt; — the goal is to make yours intentional&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Five dimensions drive placement&lt;/strong&gt; — latency, sovereignty, cost profile, operational ownership, blast radius&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat workload placement like network design&lt;/strong&gt; — optimise for the whole, not the individual node&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity is unavoidable&lt;/strong&gt; — the question is whether it's justified by the requirements&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What's Next in Cloud Without the Chaos
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Article 02 — Managed Services vs Self-Hosted: When to Hand Over the Keys&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You've placed the workload in the cloud. Now: do you manage the infrastructure yourself, or hand it to a managed service? The next article works through the real tradeoffs — including the moment a managed service saves your team 20 hours a week, and the moment it quietly doubles your incident response time.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Read the &lt;a href="https://pipelineandprompts.com/series/cloud-without-the-chaos/" rel="noopener noreferrer"&gt;Cloud Without the Chaos series index&lt;/a&gt; for the full learning path.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>hybridcloud</category>
      <category>cloudarchitecture</category>
      <category>platformengineering</category>
      <category>infrastructure</category>
    </item>
  </channel>
</rss>
