<?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: Sudhakar </title>
    <description>The latest articles on DEV Community by Sudhakar  (@sudhakar6).</description>
    <link>https://dev.to/sudhakar6</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%2F157020%2Fb99aced6-d299-457a-84c8-709fc22e6f9f.jpeg</url>
      <title>DEV Community: Sudhakar </title>
      <link>https://dev.to/sudhakar6</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sudhakar6"/>
    <language>en</language>
    <item>
      <title>How Meilisearch works</title>
      <dc:creator>Sudhakar </dc:creator>
      <pubDate>Tue, 21 Jul 2026 04:22:52 +0000</pubDate>
      <link>https://dev.to/sudhakar6/how-meilisearch-works-4126</link>
      <guid>https://dev.to/sudhakar6/how-meilisearch-works-4126</guid>
      <description>&lt;p&gt;Here is the complete end-to-end journey of searching for &lt;strong&gt;&lt;code&gt;"Evengers"&lt;/code&gt;&lt;/strong&gt; (notice the typo!) in a movie database.&lt;/p&gt;

&lt;p&gt;This flow ties together everything we've discussed—Rust application logic, LMDB offsets, Linux &lt;code&gt;mmap&lt;/code&gt;, Page Faults, and CPU memory reads.&lt;/p&gt;




&lt;h2&gt;
  
  
  The End-to-End Execution Flow
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;1. API Ingestion &amp;amp; Typo Tolerance:&lt;/strong&gt; User Space (Rust App).
The HTTP request &lt;code&gt;GET /indexes/movies/search?q=Evengers&lt;/code&gt; hits the Meilisearch Rust web server.&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tokenization:&lt;/strong&gt; Meilisearch cleans and extracts the token &lt;code&gt;"evengers"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FST Lookup:&lt;/strong&gt; Meilisearch queries a memory structure called a &lt;strong&gt;Finite State Transducer (FST)&lt;/strong&gt; to handle typos. It calculates a Levenshtein distance of 1 and maps the typo &lt;code&gt;"evengers"&lt;/code&gt; $\rightarrow$ real indexed term &lt;strong&gt;&lt;code&gt;"avengers"&lt;/code&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;2. Calculating the Virtual Address:&lt;/strong&gt; Meilisearch + LMDB Logic.
To find which movies contain &lt;code&gt;"avengers"&lt;/code&gt;, Meilisearch needs to read the &lt;strong&gt;Inverted Index&lt;/strong&gt; entry for that word.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;LMDB knows &lt;code&gt;"avengers"&lt;/code&gt; sits at &lt;strong&gt;Offset `0x8400&lt;/strong&gt;` inside the database file. Meilisearch performs pointer math:&lt;/p&gt;

&lt;p&gt;$$\text{Virtual Address} = \text{Base Address } (0\text{x}10000000) + \text{Offset } (0\text{x}8400) = 0\text{x}10008400$$&lt;/p&gt;

&lt;p&gt;Meilisearch asks the CPU: &lt;em&gt;"Read memory at &lt;code&gt;0x10008400&lt;/code&gt;."&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;3. Kernel Memory Check &amp;amp; Page Fault:&lt;/strong&gt; Linux Kernel + Hardware.
The CPU intercepting &lt;code&gt;0x10008400&lt;/code&gt; checks the &lt;strong&gt;Page Table&lt;/strong&gt; in physical RAM:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cold Search (Present Bit = 0):&lt;/strong&gt; The CPU raises a &lt;strong&gt;Page Fault&lt;/strong&gt;. The Linux OS pauses the thread, fetches the 4 KB page containing the &lt;code&gt;"avengers"&lt;/code&gt; index node off the &lt;strong&gt;SSD&lt;/strong&gt;, loads it into &lt;strong&gt;Physical RAM&lt;/strong&gt;, and flips &lt;code&gt;Present Bit = 1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Warm Search (Present Bit = 1):&lt;/strong&gt; The data is already in Physical RAM. The CPU reads it instantly in nanoseconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;4. Bitset Intersection (Roaring Bitmaps):&lt;/strong&gt; CPU Execution.
At address &lt;code&gt;0x10008400&lt;/code&gt;, Meilisearch reads a &lt;strong&gt;Roaring Bitmap&lt;/strong&gt;—a compressed set of document IDs containing the word &lt;code&gt;"avengers"&lt;/code&gt; (e.g., &lt;code&gt;[Doc ID 12, Doc ID 45, Doc ID 890]&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you also searched for another word (e.g., &lt;code&gt;"marvel"&lt;/code&gt;), Meilisearch would fetch both bitmaps and execute a &lt;strong&gt;CPU bitwise &lt;code&gt;AND&lt;/code&gt; instruction&lt;/strong&gt; to instantly find only the movies containing both terms.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;5. Ranking &amp;amp; Document Retrieval:&lt;/strong&gt; LMDB + Memory Read.
Meilisearch runs its ranking rules (Typo score, Word matching, Proximity) on the matching IDs. &lt;strong&gt;Doc ID 45&lt;/strong&gt; (&lt;em&gt;The Avengers&lt;/em&gt;, 2012) wins top spot.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now it needs the actual movie details (Title, Plot, Poster URL):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Calculates: &lt;code&gt;Virtual Address = Base Address + Offset_for_Doc_45&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Reads the raw JSON bytes directly from memory (via RAM or a final Page Fault from SSD).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;6. HTTP Response:&lt;/strong&gt; Network Output.
Meilisearch serializes the JSON payload into an HTTP &lt;code&gt;200 OK&lt;/code&gt; response and hands it off to the network socket back to the client—all completed in under 15 milliseconds.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  The Big Picture Takeaway
&lt;/h2&gt;

&lt;p&gt;Notice how Meilisearch never wrote a single line of code to handle reading from the SSD, managing buffer caches, or allocating disk blocks.&lt;/p&gt;

&lt;p&gt;It simply performed &lt;strong&gt;pointer math&lt;/strong&gt; (&lt;code&gt;Base + Offset&lt;/code&gt;), asked for a &lt;strong&gt;Virtual Address&lt;/strong&gt;, and let the &lt;strong&gt;Linux Kernel&lt;/strong&gt; handle fetching bytes off the SSD whenever a Page Fault occurred.&lt;/p&gt;

</description>
      <category>meili</category>
      <category>semanticsearch</category>
      <category>search</category>
    </item>
    <item>
      <title>AWS IMDS hop limit: the one-line change I wouldn't sign off until I understood it</title>
      <dc:creator>Sudhakar </dc:creator>
      <pubDate>Sat, 18 Jul 2026 08:39:55 +0000</pubDate>
      <link>https://dev.to/sudhakar6/aws-imds-hop-limit-the-one-line-change-i-wouldnt-sign-off-on-until-i-understood-it-393c</link>
      <guid>https://dev.to/sudhakar6/aws-imds-hop-limit-the-one-line-change-i-wouldnt-sign-off-on-until-i-understood-it-393c</guid>
      <description>&lt;p&gt;&lt;em&gt;A cryptic one-line change request — "bump the IMDS hop limit from 1 to 2 for CastAI" — and the trail of questions it took to approve it with a straight face.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The change request, in full:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Increase IMDS hop limit 1 → 2 on the EKS nodes.&lt;/strong&gt; Required for CastAI configuration. Requires node patching. Possible downtime. Approver: you.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That's it. No rationale, no risk assessment, just an integer that needs to go from 1 to 2 on production nodes, a third-party name you don't remember installing, and the word &lt;em&gt;downtime&lt;/em&gt; sitting there uncommented. You could approve it in two clicks. But your name is on it, and you have a rule: don't sign off on a production change you can't explain to whoever you'd have to wake up if it went sideways.&lt;/p&gt;

&lt;p&gt;(If you've &lt;em&gt;not&lt;/em&gt; had the tidy version, you may have met the same problem from the other end: a pod that won't start, logging &lt;code&gt;could not get EC2 instance identity metadata: context deadline exceeded&lt;/code&gt;, or half your workloads stuck &lt;code&gt;Pending&lt;/code&gt; after an EKS upgrade. Same root cause, less polite packaging.)&lt;/p&gt;

&lt;p&gt;So instead of approving it, you start pulling the thread. This is where it goes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;In a hurry? The answer, before the story.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The symptoms — a &lt;code&gt;context deadline exceeded&lt;/code&gt; timeout, pods stuck &lt;code&gt;Pending&lt;/code&gt;, or a CastAI change request — all point at one setting: the &lt;strong&gt;IMDS hop limit&lt;/strong&gt;, which needs to go from &lt;strong&gt;1 → 2&lt;/strong&gt; on the EKS nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IMDS lives locally on each EC2 instance&lt;/strong&gt; (&lt;code&gt;169.254.169.254&lt;/code&gt;) — not in Kubernetes, not the public AWS API. That locality is the whole reason hops matter.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;hop limit&lt;/strong&gt; is the TTL on IMDS's response; it caps how many network layers a request may cross (range 1–64).&lt;/li&gt;
&lt;li&gt;A pod sits in its &lt;strong&gt;own Linux network namespace&lt;/strong&gt;, so pod → IMDS costs &lt;strong&gt;2 hops&lt;/strong&gt;. At limit 1 the request dies at the host; limit 2 lets it through.&lt;/li&gt;
&lt;li&gt;CastAI's &lt;strong&gt;&lt;code&gt;spot-handler&lt;/code&gt;&lt;/strong&gt; (a per-node DaemonSet) reads IMDS to catch spot-interruption notices — the concrete thing the limit is blocking.&lt;/li&gt;
&lt;li&gt;Limit 1 blocks &lt;strong&gt;every&lt;/strong&gt; pod equally; normal app pods just don't notice, because they use &lt;strong&gt;IRSA / EKS Pod Identity&lt;/strong&gt; instead of IMDS.&lt;/li&gt;
&lt;li&gt;Applying it means &lt;strong&gt;replacing the nodes&lt;/strong&gt; (the setting lives in the launch template) via a rolling drain — hence the downtime caveat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything below is the &lt;em&gt;why&lt;/em&gt; — the trail one engineer followed to be able to defend that change at 3 a.m.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  First, what is IMDS — and why does anything need it?
&lt;/h2&gt;

&lt;p&gt;Every line of that request leans on one acronym — &lt;strong&gt;IMDS&lt;/strong&gt; — so that's where the thread starts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;IMDS is the EC2 Instance Metadata Service&lt;/strong&gt; — a small, read-only service any EC2 instance can query to learn facts about &lt;em&gt;itself&lt;/em&gt;: its instance ID, type, Availability Zone, networking, and most importantly the &lt;strong&gt;temporary IAM credentials&lt;/strong&gt; for whatever role is attached to it.&lt;/p&gt;

&lt;p&gt;Why does that need to exist? Because the alternative is what gets people breached: long-lived AWS access keys baked into code or dropped on disk, rotated by hand until one leaks. IMDS removes that pattern. Code on the instance asks "what are my credentials right now?" and gets back short-lived, auto-rotating ones — no secrets in the repo, no manual rotation. That single idea, &lt;em&gt;self-identity and keyless credentials on demand&lt;/em&gt;, is what the whole story is built around. The hop limit, containers, and the security argument later are all plumbing around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where IMDS actually lives
&lt;/h2&gt;

&lt;p&gt;Before going further, it's worth killing the most common wrong mental picture. IMDS is &lt;strong&gt;not&lt;/strong&gt; a pod in your cluster, and it is &lt;strong&gt;not&lt;/strong&gt; the public AWS API you reach over the internet.&lt;/p&gt;

&lt;p&gt;It's a &lt;strong&gt;link-local endpoint&lt;/strong&gt; — &lt;code&gt;169.254.169.254&lt;/code&gt; (IPv6: &lt;code&gt;fd00:ec2::254&lt;/code&gt;) — served &lt;strong&gt;locally, on each individual EC2 instance&lt;/strong&gt;, by the underlying EC2/Nitro platform. "Link-local" is the operative word: the request never actually leaves the machine to reach a remote server. The instance turns to itself and asks "who am I, and what may I do?", and the platform answers from right there on the box.&lt;/p&gt;

&lt;p&gt;That locality is the load-bearing fact. Because each instance serves its &lt;em&gt;own&lt;/em&gt; IMDS, a request only succeeds if it arrives from that instance's own network — the caller effectively has to be standing on the box.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;❓&lt;/strong&gt; &lt;em&gt;Which raises the obvious question of distance: how far from the box may a caller sit before a request is refused?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So what is a "hop limit"?
&lt;/h2&gt;

&lt;p&gt;The hop limit borrows straight from how IP networking already works. A &lt;em&gt;hop&lt;/em&gt; is one pass through a router or network layer; every IP packet carries a TTL ("time to live") counter, each hop knocks it down by one, and when it hits zero the packet is dropped. It's the mechanism that stops lost packets from circling forever.&lt;/p&gt;

&lt;p&gt;IMDS reuses exactly that. The "hop limit" is the TTL stamped on the metadata service's responses, and it decides one thing: how many network layers a metadata request may cross before the packet dies.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default:&lt;/strong&gt; 1 on a plain EC2 instance — deliberately, so the response can't leave the box it came from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum:&lt;/strong&gt; 64.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where it's set:&lt;/strong&gt; a field called &lt;code&gt;HttpPutResponseHopLimit&lt;/code&gt; on the instance's metadata options (and on the launch template that stamps out new instances).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a limit of 1 means the answer isn't allowed to travel even one layer away from the instance. That sounds harmless — until you remember everything you run is in a container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Clarification — the thing that's easy to get wrong here&lt;/strong&gt;&lt;br&gt;
"IMDSv2 sets the hop limit to 1" is not right. The hop limit is an &lt;strong&gt;independent&lt;/strong&gt; setting; IMDSv2 is a separate hardening, based on session tokens (more on that when we get to the security trade-off). They're usually configured together, which is why people treat them as one thing. And "the default is 1" has big exceptions: &lt;strong&gt;EKS managed node groups have defaulted the hop limit to 2 since 2020&lt;/strong&gt;, and &lt;strong&gt;Amazon Linux 2023 AMIs default to 2&lt;/strong&gt; on purpose, for containers. So the real question isn't "why is 1 the default" — it's &lt;em&gt;why are our nodes at 1?&lt;/em&gt; Someone hardened them, or they came up a non-standard path. That's the first hint the change has a backstory.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🤔&lt;/strong&gt; &lt;em&gt;If containers are everywhere and a limit of 1 forbids leaving the box, how has anything been working?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why a container is different
&lt;/h2&gt;

&lt;p&gt;A process running directly on the host reaches the metadata service in a single hop: process → &lt;code&gt;169.254.169.254&lt;/code&gt;. But almost nothing runs directly on the host. It runs in a pod, and a pod doesn't share the host's network — it lives in its own isolated network sandbox, with its own virtual interface wired to the host through a virtual cable (a &lt;code&gt;veth&lt;/code&gt; pair into a bridge). Crossing that cable is itself a hop.&lt;/p&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%2Frex2conm2v2h3s4cfbbl.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%2Frex2conm2v2h3s4cfbbl.png" alt="Two hops (pod → host → IMDS)" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Two hops, every time. So with the limit at 1, the packet's TTL hits zero the moment it reaches the host — one hop short. It doesn't fail loudly; it just times out. Set the limit to 2 and both jumps are allowed. That's the entire mechanism: a container sits one network layer too far from the metadata service, and the hop limit is the ruler measuring the gap.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deep dive — the "extra layer" is a real kernel object&lt;/strong&gt;&lt;br&gt;
It's a &lt;strong&gt;Linux network namespace&lt;/strong&gt; with a &lt;code&gt;veth&lt;/code&gt; pair bridged to the host. That boundary is the first hop, full stop. Hold onto the phrase &lt;em&gt;Linux network namespace&lt;/em&gt; — a second, unrelated thing also called a "namespace" shows up later, and the two get confused constantly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧵&lt;/strong&gt; &lt;em&gt;That clears up the mechanism — but it leaves a loose end: if AWS runs this metadata service, why is **your&lt;/em&gt;* team the one deciding its hop limit?*&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Who actually maintains it — AWS or you
&lt;/h2&gt;

&lt;p&gt;Cleanly split:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;What they handle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The service itself&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AWS (internal)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Runs and maintains IMDS and the &lt;code&gt;169.254.169.254&lt;/code&gt; endpoint. Nothing to patch, host, or babysit.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The configuration&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;You (the engineer)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Whether IMDSv2 is required, the &lt;strong&gt;hop limit&lt;/strong&gt;, whether the endpoint is on at all, and any IAM/network restrictions.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Mechanically: attach an IAM role to an instance, and AWS mints temporary, auto-rotating credentials for it; the AWS SDKs inside your app fetch those from &lt;code&gt;169.254.169.254&lt;/code&gt;. No static keys, no manual rotation.&lt;/p&gt;

&lt;p&gt;So there's nothing to keep alive at 3 a.m. — but the dials that decide &lt;em&gt;how&lt;/em&gt; your instances talk to IMDS are yours, and the hop limit is one of them. Which means the request isn't asking AWS for anything — it's asking you to loosen how tightly your own nodes are locked down.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🧐&lt;/strong&gt; &lt;em&gt;That makes the one clue you skipped worth chasing: **required for CastAI configuration&lt;/em&gt;* — and you've never installed anything called CastAI.*&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The name in the request: what CastAI is, and why it forces the change
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;CastAI is a Kubernetes automation platform for cloud cost optimization.&lt;/strong&gt; It watches your cluster continuously and, on its own, right-sizes workloads, bin-packs pods onto fewer nodes, and swaps pricey on-demand instances for cheaper (often Spot) ones — trimming the EC2 bill without anyone hand-tuning it.&lt;/p&gt;

&lt;p&gt;And here's the part where it all clicks. To do that, one of CastAI's components runs as a pod &lt;strong&gt;on every node&lt;/strong&gt; and has to read that node's own IMDS. That single dependency is the whole origin of the change — because reading IMDS from inside a pod is exactly what a hop limit of 1 blocks. Laid end to end, the request explains itself:&lt;/p&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%2Fpqxuzxv0kqthasxfv0kb.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%2Fpqxuzxv0kqthasxfv0kb.png" alt="Causal chain (ends on the green " width="800" height="738"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's the &lt;em&gt;why&lt;/em&gt;. But approving a change isn't about confirming the why — it's about confirming it's &lt;em&gt;safe&lt;/em&gt;. Two questions now stand between you and the signature: what exactly does that per-node pod read, and is opening this door dangerous? Everything below is those two questions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🤔&lt;/strong&gt; &lt;em&gt;Starting with a reflexive worry: if CastAI has a pod on every node, is your traffic flowing through it?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  It's a watcher, not a checkpoint
&lt;/h2&gt;

&lt;p&gt;Good instinct, wrong picture — worth ruling out before it poisons everything downstream. A tempting model is that CastAI sits in the request path and everything routes through it. It doesn't. CastAI is not a reverse proxy, sidecar, or firewall in your traffic path. A user hits your app pod, the traffic goes straight to your app; CastAI never sees it.&lt;/p&gt;

&lt;p&gt;What it actually is: a background observer and optimizer that pulls from two sources and correlates them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Kubernetes API server&lt;/strong&gt; — how many pods are running, what CPU/memory they requested, whether any are stuck &lt;code&gt;Pending&lt;/code&gt; because nodes are full.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cloud provider&lt;/strong&gt; — instance types, pricing, capacity, and (per node) spot-interruption signals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From those it decides things like "this workload fits on a cheaper instance type" or "drain this spot node before AWS reclaims it." That work is spread across several pods — and only one of them is why the hop limit matters:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔍 Under the hood — which CastAI component actually touches node IMDS&lt;/strong&gt;&lt;br&gt;
CastAI isn't one pod; it installs several into the &lt;strong&gt;&lt;code&gt;castai-agent&lt;/code&gt;&lt;/strong&gt; namespace, and only one is what the hop limit gates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;castai-agent&lt;/code&gt; — the read-only core (a &lt;strong&gt;Deployment&lt;/strong&gt;); talks to the K8s API and streams telemetry to CastAI.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;castai-cluster-controller&lt;/code&gt; — carries out scaling/node actions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;castai-evictor&lt;/code&gt;, &lt;code&gt;castai-pod-mutator&lt;/code&gt; / &lt;code&gt;pod-pinner&lt;/code&gt; — bin-packing and right-sizing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;castai-kvisor&lt;/code&gt; — the &lt;strong&gt;security&lt;/strong&gt; agent (KSPM / runtime security; can run an eBPF DaemonSet). It's a DaemonSet too, so people point to it for the IMDS dependency — but it isn't the one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;castai-spot-handler&lt;/code&gt; — a DaemonSet on every node that reads IMDS&lt;/strong&gt; (&lt;code&gt;/latest/meta-data/spot/instance-action&lt;/code&gt;) to catch spot-interruption notices. &lt;strong&gt;This is the pod whose per-node IMDS access the hop limit gates.&lt;/strong&gt; It only watches and reports; it doesn't drain nodes itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;So CastAI is many pods, and the spot-handler is the one that needs IMDS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;❓&lt;/strong&gt; &lt;em&gt;Which raises a smaller question you can't unsee now: people keep saying **agent&lt;/em&gt;* — is that a pod, or something lower-level running straight on the machine?*&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  "Agent" is just a role — the real actor is the DaemonSet
&lt;/h2&gt;

&lt;p&gt;"Agent" is a role, not a privileged bare-metal binary. In Kubernetes it's still a container in a pod. What matters is which kind of workload wraps it, and two native Kubernetes shapes tell the story:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt; — the centralized-coordinator shape (the core &lt;code&gt;castai-agent&lt;/code&gt;): a replica or two, somewhere in the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DaemonSet&lt;/strong&gt; — Kubernetes guarantees exactly one copy on every worker node. Grow from 5 nodes to 50 and it drops a copy onto each newcomer automatically. It's the standard shape for per-node infrastructure: log shippers, security scanners, node-termination handlers — and CastAI's &lt;code&gt;spot-handler&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's why the problem is per-node: there's a spot-handler pod on each node, each one reaching for that node's IMDS from inside its own pod network. Fixing one node fixes one node — this is a fleet-wide job.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deep dive — you've seen this shape in AWS's own tooling&lt;/strong&gt;&lt;br&gt;
&lt;code&gt;aws-node-termination-handler&lt;/code&gt; is the identical pattern: a small DaemonSet pod on each host polling IMDS paths like &lt;code&gt;/spot&lt;/code&gt; and &lt;code&gt;/events&lt;/code&gt;, cordoning and draining the node when a notice arrives. CastAI's spot-handler is a specialized cousin.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;🧐&lt;/strong&gt; &lt;em&gt;Something still doesn't add up, though: CastAI already has a direct line to the Kubernetes API, which knows everything about pods and nodes — so why does it need to crawl down to each node's AWS metadata at all?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What Kubernetes can't see
&lt;/h2&gt;

&lt;p&gt;Your instinct is half right, and the missing half is the point. Kubernetes knows the logical truth — this pod asked for 2 CPUs and 4 GB, that node is 80% full. It's blind to money and physical hardware.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes says: "this pod needs 2 CPU / 4 GB."&lt;/li&gt;
&lt;li&gt;The cloud says: "you're on an &lt;code&gt;m5.large&lt;/code&gt;, on-demand, in &lt;code&gt;us-east-1&lt;/code&gt;, at $X/hour — and there's a cheaper Spot option that fits."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A cost optimizer has to marry those two datasets, so it needs cloud-side facts Kubernetes can't supply. But the tidy version of this oversells IMDS's role:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Nuance — it's not pricing that needs your node's IMDS&lt;/strong&gt;&lt;br&gt;
"CastAI reads instance types and prices from IMDS on every node" is wrong in the load-bearing detail. Instance catalogs and pricing come mostly from &lt;strong&gt;cloud provider APIs and CastAI's own pricing database&lt;/strong&gt; (via the cluster-controller's IAM permissions) — not per-node IMDS. The thing that genuinely must be read from a node's own IMDS is &lt;strong&gt;live, node-local state&lt;/strong&gt;, and the headline example is the &lt;strong&gt;spot-interruption notice&lt;/strong&gt; the spot-handler watches for — real-time, specific to that box, available nowhere but its own &lt;code&gt;169.254.169.254&lt;/code&gt;. So the honest sentence is: the hop-limit change unblocks node-local IMDS reads (spot interruption chief among them), not CastAI's whole pricing engine.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So the spot-handler needs its node's IMDS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🤔❓&lt;/strong&gt; &lt;em&gt;Which finally detonates the question that should have been nagging since the container section: every normal app pod lives behind the exact same container network, so they're all two hops from IMDS too — why has a hop limit of 1 been "working" for them, and only breaks for CastAI?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The turn: a hop limit of 1 was never working for anyone
&lt;/h2&gt;

&lt;p&gt;This is the part most explanations get subtly wrong. The comfortable assumption — that hop limit 1 is fine for normal pods — is false. It was never fine. A hop limit of 1 blocks every pod equally, CastAI's and yours. There's no per-app exemption.&lt;/p&gt;

&lt;p&gt;The reason your app pods never complained is that they don't call IMDS at all.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Well-configured EKS apps get AWS credentials through &lt;strong&gt;IRSA (IAM Roles for Service Accounts)&lt;/strong&gt; or the newer &lt;strong&gt;EKS Pod Identity&lt;/strong&gt;. AWS injects a short-lived token straight into the container; the SDK finds it and talks to regional AWS endpoints — never touching &lt;code&gt;169.254.169.254&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Because they never knock on IMDS, they never hit the wall. They've been unaware it exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the wall exists on purpose — it's a security control. If an attacker compromises a public-facing pod, a hop limit of 1 stops them from reaching &lt;code&gt;169.254.169.254&lt;/code&gt; to steal the node's IAM credentials. It keeps a single compromised pod from becoming a compromised node.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Clarification — "normal pods don't use IMDS" is only half true&lt;/strong&gt;&lt;br&gt;
Plenty of &lt;em&gt;system&lt;/em&gt; pods do, and they break at hop limit 1 just like CastAI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;EBS CSI driver&lt;/strong&gt; reads instance identity from IMDS; at limit 1 in some setups, volumes fail to attach and pods hang in &lt;code&gt;Pending&lt;/code&gt; (often the real cause of that post-upgrade outage).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot / node-termination handlers&lt;/strong&gt; (AWS's and CastAI's) poll IMDS for interruption notices.&lt;/li&gt;
&lt;li&gt;Some overlay &lt;strong&gt;CNIs&lt;/strong&gt; need even more — Cilium can require a hop limit of 3.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly why EKS managed node groups default to 2. The honest framing isn't "CastAI is uniquely needy" — it's that CastAI's spot-handler joins the club of node agents that need IMDS, and your nodes happen to be locked at 1.&lt;/p&gt;
&lt;/blockquote&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%2F234vdrl8uidjwtjpx4ec.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%2F234vdrl8uidjwtjpx4ec.png" alt="Compromised pod → BLOCKED (red) vs ALLOWED (green)" width="800" height="352"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🧵&lt;/strong&gt; &lt;em&gt;Two new threads pull at you: is IRSA-versus-IMDS a switch someone flips, and if a hacker can still use IRSA, why is that any less of a catastrophe?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Two keys: one opens the machine, one opens a drawer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is IRSA a toggle?&lt;/strong&gt; No. There's no "use IRSA instead of IMDS" checkbox. It's an architecture baked into the AWS SDK's credential provider chain — the SDK checks credential sources in a fixed order, and IRSA's injected token sits near the top. You wire it up by:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Creating a Kubernetes &lt;strong&gt;ServiceAccount&lt;/strong&gt; annotated with an &lt;strong&gt;IAM Role ARN&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Attaching that ServiceAccount to the pod.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At launch, Kubernetes mounts a signed token into the container and points an env var at it. The SDK uses that first and never falls through to IMDS. (EKS Pod Identity reaches the same end by a newer route.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;So why isn't a stolen IRSA token as bad as a stolen IMDS one?&lt;/strong&gt; Blast radius:&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;strong&gt;Node IMDS credentials&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;IRSA / Pod Identity credentials&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Whose identity&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;entire EC2 node's&lt;/strong&gt; IAM role&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;One pod's&lt;/strong&gt; narrowly-scoped role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical power&lt;/td&gt;
&lt;td&gt;Broad — attach ENIs, pull images, edit routes, talk to the control plane&lt;/td&gt;
&lt;td&gt;Only what that app needs (e.g. one S3 bucket)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If stolen&lt;/td&gt;
&lt;td&gt;Attacker can manipulate the wider AWS account&lt;/td&gt;
&lt;td&gt;Contained to that app's sandbox&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Steal an IMDS token and you have the keys to the machine; steal an IRSA token and you have the keys to one drawer. That asymmetry is why a hop limit of 1 is a sane default — and why raising it earns the scrutiny you're giving it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🤔&lt;/strong&gt; &lt;em&gt;Which lands on the question really holding up your signature: you're about to open the IMDS door for CastAI's pod — so what if CastAI's pod is the one that gets breached?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What if the breach comes through CastAI
&lt;/h2&gt;

&lt;p&gt;The honest answer first: it's possible. No software is exploit-proof, and there's no 100% guarantee. The reason security teams still approve this isn't that a breach is impossible — it's that the blast radius is boxed in on three sides:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;IMDSv2 is session-based&lt;/strong&gt; (this is the "v2" mentioned earlier). There's no static string to grab and run. An attacker must first &lt;code&gt;PUT&lt;/code&gt; for a short-lived token, then pass it in a header on the &lt;code&gt;GET&lt;/code&gt;. The token expires quickly and won't work outside that instance's network boundary — copying it to a laptop gets nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CastAI's IAM role is scoped tight&lt;/strong&gt; — infrastructure/metadata actions (describe instances, node scaling), not your customer databases, app secrets, or private buckets. Even wearing CastAI's identity, an attacker inherits only CastAI's narrow powers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Namespace isolation + Network Policies&lt;/strong&gt; — CastAI lives alone in the &lt;code&gt;castai-agent&lt;/code&gt; namespace. With standard Kubernetes NetworkPolicies enforced, cross-namespace traffic can be blocked, so an intruder in the CastAI pod can't freely pivot into your app pods.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Stated plainly: raising the hop limit to 2 does widen network visibility for node agents. It's a calculated risk — defended by IMDSv2 session tokens, least-privilege IAM, and namespace/network isolation — taken in exchange for continuous, automated cost savings. That's a sentence you can defend at 3 a.m.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🧠&lt;/strong&gt; &lt;em&gt;You've now said "namespace" several times, and a splinter of doubt lodges: isn't "namespace" also a Linux kernel thing from containers? Are these the same word doing two jobs?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a id="two-namespaces"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things named "namespace" — and they're not the same
&lt;/h2&gt;

&lt;p&gt;They share a word, they're deeply connected, and they operate in completely different universes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux namespace — the real isolation technology, at the kernel level.&lt;/strong&gt; It makes a process believe it's alone on the machine. Containers &lt;em&gt;are&lt;/em&gt; Linux namespaces wrapped around a process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network (&lt;code&gt;net&lt;/code&gt;)&lt;/strong&gt; — its own interfaces, loopback, IP. This is the extra hop from earlier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PID&lt;/strong&gt; — the container thinks its main process is PID 1, though the host sees PID 48291.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mount (&lt;code&gt;mnt&lt;/code&gt;)&lt;/strong&gt; — sees only its own filesystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No Linux namespaces, no containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes namespace — an organizational label, at the orchestration level.&lt;/strong&gt; A K8s namespace (&lt;code&gt;default&lt;/code&gt;, &lt;code&gt;castai-agent&lt;/code&gt;, …) is just a tag in the Kubernetes database (etcd). The Linux kernel has never heard of it. It exists to group resources, scope RBAC, and set quotas — a locked department suite inside an office building.&lt;/p&gt;

&lt;p&gt;Where they meet is your exact problem:&lt;/p&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%2F5llsvw8i7dkb1wgms9mi.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%2F5llsvw8i7dkb1wgms9mi.png" alt="The two " width="800" height="702"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes supplies the organizational wrapper (the K8s namespace); the Linux network namespace is what physically creates the hop that forces &lt;code&gt;hopLimit = 2&lt;/code&gt;. Same word, two jobs, only one of them relevant to your change.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deep dive — the escape hatch you're choosing not to use&lt;/strong&gt;&lt;br&gt;
A pod can set &lt;code&gt;hostNetwork: true&lt;/code&gt;, dropping it into the host's network namespace — no pod net namespace, so it reaches IMDS in a single hop and ignores the limit. Some infra agents do this. CastAI keeps its components in the normal pod network (so it can map workloads cleanly), which is why the fix is a node-level &lt;code&gt;hopLimit=2&lt;/code&gt; rather than a &lt;code&gt;hostNetwork&lt;/code&gt; special case.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The mechanism is fully understood now. What's left is operational — and one piece of it decides whether Sunday's window really needs the word &lt;em&gt;downtime&lt;/em&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🤔&lt;/strong&gt; &lt;em&gt;How hard is this pod hitting IMDS, anyway — is it a cron job?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Not a cron job — how often it really talks
&lt;/h2&gt;

&lt;p&gt;A CronJob wakes on a timer, runs once, exits. CastAI can't work that way — cost, traffic, and scheduling pressure change by the second — so it runs as long-lived daemons, talking to different systems at different rhythms:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cluster state (pods/nodes)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Real-time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;K8s API &lt;strong&gt;watchers&lt;/strong&gt; — events pushed the instant a pod goes unschedulable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node identity (ID / type / region)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Once at boot, then cached&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A single IMDSv2 &lt;code&gt;GET&lt;/code&gt; — the hardware doesn't change while the node lives&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spot-interruption notice&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Continuous light loop&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IMDS &lt;code&gt;/spot/instance-action&lt;/code&gt; on the node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster metrics&lt;/td&gt;
&lt;td&gt;Short interval (impl. detail)&lt;/td&gt;
&lt;td&gt;Metrics scrape&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The spot loop is continuous because AWS gives little warning — up to ~2 minutes on AWS (≈30 seconds on GCP/Azure) — before reclaiming a Spot node; CastAI also runs an ML prediction model for a longer heads-up. (You'll see "every 15 seconds" quoted for metrics; treat it as approximate.)&lt;/p&gt;

&lt;p&gt;The reassuring part: the node-identity read this change is really about happens once, at pod startup. This isn't a pod hammering the metadata service in a hot loop — it's a pod that needs one clean answer when it boots, and today, on your nodes, that answer never arrives.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;❓&lt;/strong&gt; &lt;em&gt;Last question, and it's the one that governs the maintenance window: to change the limit on a running cluster, do you have to replace the nodes — and is that where the downtime comes from?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Applying it: why the nodes have to be replaced
&lt;/h2&gt;

&lt;p&gt;Yes. To land this on the existing cluster, the nodes have to be replaced (or at least relaunched).&lt;/p&gt;

&lt;p&gt;The hop limit is baked into the EC2 launch template — the blueprint the node group / Auto Scaling Group uses to build nodes. Edit the blueprint from 1 to 2 and the already-running instances don't change; the new value only applies to newly launched nodes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The exception worth knowing:&lt;/strong&gt; on a single running instance you can flip it live, no reboot: &lt;code&gt;aws ec2 modify-instance-metadata-options --http-put-response-hop-limit 2&lt;/code&gt;. But for a fleet you fix the launch template and roll the nodes, so the change is consistent and survives future scale-ups.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The choreography is a rolling replacement, not a big-bang reboot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cordon&lt;/strong&gt; — mark an old node &lt;code&gt;SchedulingDisabled&lt;/code&gt; so no new pods land there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drain&lt;/strong&gt; — evict its pods; Kubernetes reschedules them onto other nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminate &amp;amp; replace&lt;/strong&gt; — once it's empty, kill it; the ASG launches a fresh node from the updated blueprint, now at hop limit 2.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;And that explains the word that stopped you on Friday. Why "possible downtime"?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-replica workloads&lt;/strong&gt; — a one-pod app blinks out while it's killed on the old node and recreated on the new one. Mitigation: ≥2 replicas plus a PodDisruptionBudget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacity churn&lt;/strong&gt; — moving many pods at once can cause brief blips while replacement nodes pass health checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-architected service with multiple replicas and sane PDBs rides the roll with zero user-visible impact. The "possible downtime" line is there for the single-replica and tight-capacity cases — which you can now go check by name instead of guessing.&lt;/p&gt;

&lt;p&gt;At which point you know what the change does, why it exists, which pod needs it, why it was blocked, what it risks, and how to do it without taking the site down. So you approve it — not because you were told to, but because you can finally explain it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The whole picture, in one diagram
&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%2Fa9e97i82ru555iqtba9n.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%2Fa9e97i82ru555iqtba9n.png" alt="The whole picture (K8s API + CastAI cloud + node with agent/spot-handler → IMDS, 1 blocked / 2 allowed)" width="799" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What common explanations get wrong
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;"The hop limit default is always 1."&lt;/em&gt; → Plain EC2, yes; but EKS managed node groups and Amazon Linux 2023 default to 2. Nodes at 1 are usually hardened/custom (or a spot/Karpenter path).&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"IMDSv2 sets the hop limit."&lt;/em&gt; → The hop limit is an independent option; IMDSv2 is the session-token requirement. Configured together, but distinct.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"The kvisor DaemonSet needs IMDS."&lt;/em&gt; → The per-node IMDS consumer is the &lt;code&gt;castai-spot-handler&lt;/code&gt; DaemonSet (monitoring-only). &lt;code&gt;kvisor&lt;/code&gt; is the security agent.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"CastAI reads instance type &amp;amp; pricing from node IMDS."&lt;/em&gt; → Type/pricing come mostly from cloud APIs + CastAI's pricing DB. The essential node-local IMDS read is the spot-interruption notice.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;"Normal pods don't hit the hop wall."&lt;/em&gt; → Only if they use IRSA. System pods (EBS CSI, termination handlers, some CNIs) do, and fail at limit 1 — the reason EKS defaults to 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Read further
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS — &lt;em&gt;Use the Instance Metadata Service&lt;/em&gt; (hop limit / IMDSv2): &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS — &lt;em&gt;Instance metadata access considerations&lt;/em&gt; (container / hop-limit-2 guidance): &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS — &lt;em&gt;Amazon EKS now supports IMDSv2&lt;/em&gt; (why EKS node groups default to hop limit 2): &lt;a href="https://aws.amazon.com/about-aws/whats-new/2020/08/amazon-eks-supports-ec2-instance-metadata-service-v2" rel="noopener noreferrer"&gt;https://aws.amazon.com/about-aws/whats-new/2020/08/amazon-eks-supports-ec2-instance-metadata-service-v2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Amazon Linux 2023 — &lt;em&gt;IMDSv2&lt;/em&gt; (AL2023 defaults hop limit to 2): &lt;a href="https://docs.aws.amazon.com/linux/al2023/ug/imdsv2.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/linux/al2023/ug/imdsv2.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CastAI Docs — &lt;em&gt;Read-only agent&lt;/em&gt; &amp;amp; &lt;em&gt;Spot handler&lt;/em&gt;: &lt;a href="https://docs.cast.ai/docs/about-the-read-only-agent" rel="noopener noreferrer"&gt;https://docs.cast.ai/docs/about-the-read-only-agent&lt;/a&gt; · &lt;a href="https://docs.cast.ai/docs/spot-handler" rel="noopener noreferrer"&gt;https://docs.cast.ai/docs/spot-handler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS — &lt;em&gt;aws-node-termination-handler&lt;/em&gt; (the same DaemonSet-reads-IMDS pattern): &lt;a href="https://github.com/aws/aws-node-termination-handler" rel="noopener noreferrer"&gt;https://github.com/aws/aws-node-termination-handler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes — &lt;em&gt;Namespaces&lt;/em&gt;: &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Linux — &lt;em&gt;namespaces(7)&lt;/em&gt;: &lt;a href="https://man7.org/linux/man-pages/man7/namespaces.7.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man7/namespaces.7.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS EKS — &lt;em&gt;IRSA&lt;/em&gt; &amp;amp; &lt;em&gt;EKS Pod Identity&lt;/em&gt;: &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>imds</category>
      <category>kubernetes</category>
      <category>ec2</category>
    </item>
    <item>
      <title>CastAI, AWS IMDS IRSA EC2, K8S</title>
      <dc:creator>Sudhakar </dc:creator>
      <pubDate>Sat, 18 Jul 2026 07:17:54 +0000</pubDate>
      <link>https://dev.to/sudhakar6/castai-aws-imds-irsa-ec2-k8s-3j4e</link>
      <guid>https://dev.to/sudhakar6/castai-aws-imds-irsa-ec2-k8s-3j4e</guid>
      <description>&lt;h1&gt;
  
  
  From a Timeout Error to Patching EKS Nodes: An IMDS Rabbit Hole
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;A learning journey through AWS Instance Metadata, container networking, CastAI, Kubernetes vs. Linux namespaces, and the security reasoning behind a one-line node config change.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The 2AM version of why you're here
&lt;/h2&gt;

&lt;p&gt;You didn't wake up curious about instance metadata. Something broke first. Maybe it looked like one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pod refuses to start and the logs read &lt;code&gt;could not get EC2 instance identity metadata: ... context deadline exceeded&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;After an EKS upgrade, half your pods are stuck &lt;strong&gt;&lt;code&gt;Pending&lt;/code&gt;&lt;/strong&gt; and the EBS CSI driver is quietly failing to attach volumes.&lt;/li&gt;
&lt;li&gt;A change request lands on your desk: &lt;em&gt;"Bump the IMDS hop limit from 1 to 2 on the EKS nodes for CastAI — requires node patching, possible downtime,"&lt;/em&gt; and you're the one who has to approve or apply it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All three trace back to the same tiny setting, and none of the one-line "just set the hop limit to 2" answers on the internet tell you &lt;strong&gt;why&lt;/strong&gt; — or whether it's safe. That "why" turns out to run through container networking, IAM, two completely different meanings of the word &lt;em&gt;namespace&lt;/em&gt;, and a real security trade-off. This article follows that thread from the error message all the way down.&lt;/p&gt;

&lt;h2&gt;
  
  
  First, what is IMDS — and why does anything need it?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;IMDS is the EC2 Instance Metadata Service.&lt;/strong&gt; It's a small, read-only service that every EC2 instance can query to learn facts about &lt;em&gt;itself&lt;/em&gt; — its instance ID, type, Availability Zone, networking details, and (most importantly) the temporary IAM credentials for whatever role is attached to it.&lt;/p&gt;

&lt;p&gt;Why does that need to exist? The alternative is ugly: baking long-lived AWS access keys into your application code or onto the server's disk, then rotating them by hand forever. Instead, code running on the instance just asks IMDS &lt;em&gt;"what are my credentials right now?"&lt;/em&gt; and gets short-lived, auto-rotating ones back. That single idea — &lt;strong&gt;self-identity and keyless credentials, on demand&lt;/strong&gt; — is what this whole article orbits. Hop limits, containers, and the security trade-offs are all just the plumbing wrapped around it.&lt;/p&gt;

&lt;h2&gt;
  
  
  And where does this "IMDS" thing actually live?
&lt;/h2&gt;

&lt;p&gt;Before anything else, kill the most common wrong mental picture. IMDS is &lt;strong&gt;not&lt;/strong&gt; a pod in your cluster, and it is &lt;strong&gt;not&lt;/strong&gt; the public AWS API / control plane you reach over the internet.&lt;/p&gt;

&lt;p&gt;It's a &lt;strong&gt;link-local endpoint&lt;/strong&gt; — &lt;code&gt;169.254.169.254&lt;/code&gt; (IPv6: &lt;code&gt;fd00:ec2::254&lt;/code&gt;) — served &lt;strong&gt;locally on each individual EC2 instance&lt;/strong&gt; by the underlying EC2/Nitro infrastructure. "Link-local" means the request never actually leaves the box to travel to some remote server; the instance asks &lt;em&gt;itself&lt;/em&gt; "who am I, and what credentials do I have?" and the platform answers.&lt;/p&gt;

&lt;p&gt;That single fact is the seed of the entire problem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Because each instance serves its &lt;strong&gt;own&lt;/strong&gt; IMDS locally, a request only reaches it from the instance's own network — in effect, the caller has to look like it's running directly on the box.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;container doesn't share the instance's network directly — it runs in its own isolated network sandbox&lt;/strong&gt; with its own virtual network interface, wired to the host through a virtual cable (a &lt;code&gt;veth&lt;/code&gt; pair into a bridge). So a packet leaving a container has to first cross that virtual boundary onto the host's network before it can go anywhere else. That crossing is the "extra layer." The moment your caller lives in a pod, "ask the instance about itself" stops being a local question and becomes a &lt;strong&gt;network hop&lt;/strong&gt; — and that's where the hop limit starts dropping packets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keep that image — &lt;em&gt;a local endpoint on the box, and a container sitting one layer away from it&lt;/em&gt; — and the rest of this article is really just unpacking its consequences.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Symptoms like &lt;code&gt;context deadline exceeded&lt;/code&gt;, pods stuck &lt;code&gt;Pending&lt;/code&gt;, or a CastAI change request all point at one setting: the &lt;strong&gt;IMDS hop limit&lt;/strong&gt;, which typically needs to go from &lt;strong&gt;1 → 2&lt;/strong&gt; on EKS nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IMDS lives locally on each EC2 instance&lt;/strong&gt; (&lt;code&gt;169.254.169.254&lt;/code&gt;), &lt;em&gt;not&lt;/em&gt; in Kubernetes and &lt;em&gt;not&lt;/em&gt; the public AWS API. That locality is why network hops matter.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;hop limit&lt;/strong&gt; is the TTL (time-to-live) on the metadata service's network response — it caps how many network layers a request can cross before packets are dropped. Default range is 1–64.&lt;/li&gt;
&lt;li&gt;A pod lives in its &lt;strong&gt;own Linux network namespace&lt;/strong&gt;, so a pod → IMDS request crosses &lt;em&gt;two&lt;/em&gt; boundaries (pod network → host → IMDS). With a limit of 1, the request dies at the host. Limit 2 lets it through.&lt;/li&gt;
&lt;li&gt;CastAI's &lt;strong&gt;spot-handler&lt;/strong&gt; runs as a DaemonSet on each node and reads IMDS to catch spot-interruption notices. That's the concrete thing the limit is blocking.&lt;/li&gt;
&lt;li&gt;Hop limit 1 isn't a CastAI-specific wall — it blocks &lt;em&gt;every&lt;/em&gt; pod equally. Most app pods just don't notice because they use &lt;strong&gt;IRSA / EKS Pod Identity&lt;/strong&gt; instead of IMDS.&lt;/li&gt;
&lt;li&gt;Applying the change means &lt;strong&gt;replacing nodes&lt;/strong&gt; (the setting lives in the launch template), done as a rolling drain — hence the downtime caveat.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each section below starts with a question that actually came up, answers it, then hands you the next doubt it triggers. Boxes marked &lt;strong&gt;Deep dive&lt;/strong&gt; and &lt;strong&gt;Clarification&lt;/strong&gt; go beyond the surface.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What does "hop limit" actually mean?
&lt;/h2&gt;

&lt;p&gt;Think of a &lt;strong&gt;hop&lt;/strong&gt; as one pass through a router or network layer. Every IP packet carries a TTL (time-to-live) counter; each hop decrements it by one, and when it hits zero the packet is dropped. IMDS uses exactly this mechanism: the "hop limit" is the TTL stamped on the metadata service's responses.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Default:&lt;/strong&gt; 1 on a plain EC2 instance. This deliberately keeps the response inside the instance — it can't be forwarded off the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maximum:&lt;/strong&gt; 64.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How you read/change it:&lt;/strong&gt; the &lt;code&gt;HttpPutResponseHopLimit&lt;/code&gt; field on the instance's metadata options (or the launch template).&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Clarification — a common oversimplification&lt;/strong&gt;&lt;br&gt;
"IMDSv2 sets the hop limit to 1" is not quite right. The hop limit is an &lt;strong&gt;independent instance setting&lt;/strong&gt;, not something IMDSv2 turns on. Two are often conflated because they're configured together. And the "default is 1" rule has big exceptions: &lt;strong&gt;EKS managed node groups have defaulted the hop limit to 2 since 2020&lt;/strong&gt;, and &lt;strong&gt;Amazon Linux 2023 AMIs default to 2&lt;/strong&gt; specifically to support containers. So if &lt;em&gt;your&lt;/em&gt; EKS nodes are sitting at 1, that's usually a hardened or custom config (or a Karpenter/spot path that didn't inherit the default) — which is exactly why an explicit change request exists.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;→ &lt;em&gt;If containers are so common on EC2, why would a limit of 1 ever block them? What's special about a container?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Why containers need a hop limit of 2
&lt;/h2&gt;

&lt;p&gt;A normal process running directly on the EC2 host reaches IMDS in &lt;strong&gt;one hop&lt;/strong&gt;: process → &lt;code&gt;169.254.169.254&lt;/code&gt;. A containerized process does not, because it lives behind an extra network layer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Container process
      │  (Hop 1)  leaves the pod's virtual network, crosses the bridge into the host
      ▼
EC2 Host OS network
      │  (Hop 2)  host forwards the request to the metadata service
      ▼
AWS IMDS (169.254.169.254)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a limit of &lt;strong&gt;1&lt;/strong&gt;, the packet's TTL is exhausted the moment it reaches the host — before it can make the second jump to IMDS. The request silently times out. Bumping to &lt;strong&gt;2&lt;/strong&gt; authorizes both jumps.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deep dive — where the extra hop physically comes from&lt;/strong&gt;&lt;br&gt;
It's not metaphorical. The container runtime places each pod in its own &lt;strong&gt;Linux network namespace&lt;/strong&gt; with a virtual ethernet pair (veth) bridged to the host. Crossing that veth boundary &lt;em&gt;is&lt;/em&gt; the first hop. We'll come back to this when we compare Kubernetes namespaces with Linux namespaces (§10) — it's the same boundary that makes the whole hop-limit problem exist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;→ &lt;em&gt;I get what IMDS is for now. But is it something AWS just runs for me, or something an engineer has to set up and maintain?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Who actually maintains IMDS — AWS or you?
&lt;/h2&gt;

&lt;p&gt;We've settled &lt;em&gt;what&lt;/em&gt; IMDS is for and &lt;em&gt;where&lt;/em&gt; it runs. The question left from the journey is ownership: do you operate this thing, or does AWS? The mechanics make the answer clear.&lt;/p&gt;

&lt;p&gt;When you attach an &lt;strong&gt;IAM role&lt;/strong&gt; to an instance, AWS mints temporary, auto-rotating credentials for it. AWS SDKs inside your app know to fetch those credentials from the IMDS link-local address (&lt;code&gt;169.254.169.254&lt;/code&gt;). No static keys to leak, no manual rotation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shared responsibility:&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;Layer&lt;/th&gt;
&lt;th&gt;Owner&lt;/th&gt;
&lt;th&gt;What they handle&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The service itself&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;AWS (internal)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Hosts and maintains IMDS and the &lt;code&gt;169.254.169.254&lt;/code&gt; endpoint. Nothing for you to patch or install.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;The configuration&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;You (the engineer)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Decide whether IMDSv2 is required, set the hop limit, enable/disable the endpoint, restrict it with IAM or network policy.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So there's no "IMDS server" to babysit — but the knobs that decide &lt;em&gt;how&lt;/em&gt; your instances talk to it are yours, and the hop limit is one of them.&lt;/p&gt;

&lt;p&gt;→ &lt;em&gt;Right — so what is CastAI, and why is a tool I might not have installed myself the reason someone wants to change my node settings?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  4. What CastAI is — and why it's the reason for the change
&lt;/h2&gt;

&lt;p&gt;We've been saying "CastAI" since the opening ticket without ever defining it. Time to fix that — because it's the whole reason the hop-limit change is on the table.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CastAI is a Kubernetes automation platform for cloud cost optimization.&lt;/strong&gt; It continuously watches your cluster and automatically right-sizes workloads, bin-packs pods onto fewer nodes, and swaps pricey on-demand instances for cheaper (often Spot) ones — trimming your EC2 bill without anyone hand-tuning it.&lt;/p&gt;

&lt;p&gt;To do that, one of CastAI's components runs as a pod &lt;strong&gt;on each node&lt;/strong&gt; and has to read that node's &lt;em&gt;own&lt;/em&gt; AWS metadata from IMDS. That single dependency is the entire origin of the ticket — because reading IMDS &lt;strong&gt;from inside a pod&lt;/strong&gt; is exactly what a hop limit of 1 blocks (§2). Put the pieces together and the change request explains itself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The ticket names CastAI
        │
        ▼
CastAI runs a per-node pod that must read the node's own IMDS
        │
        ▼
A pod → IMDS request costs 2 hops (§2) — but the nodes are capped at 1
        │
        ▼
⇒ raise the hop limit  1 → 2      ← the whole reason for the change request
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything else in this article is really just the two follow-up questions that chain provokes: &lt;em&gt;what does that per-node component actually read?&lt;/em&gt; and &lt;em&gt;is it safe to open the door?&lt;/em&gt; We'll take them in turn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First, a fair worry: does CastAI sit in my traffic path?&lt;/strong&gt; A natural mental model is &lt;em&gt;"CastAI is a pod on the node, so maybe all traffic routes through it first."&lt;/em&gt; &lt;strong&gt;It doesn't.&lt;/strong&gt; CastAI is not a reverse proxy, sidecar, or firewall in your request path. If a user hits your application pod, that traffic goes straight to your app — CastAI never sees it.&lt;/p&gt;

&lt;p&gt;Instead, CastAI is a &lt;strong&gt;background monitoring and optimization system&lt;/strong&gt;. It gathers data from two places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The Kubernetes API server&lt;/strong&gt; — "How many pods are running? What CPU/memory did they request? Are any pods stuck Pending because nodes are full?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The cloud provider&lt;/strong&gt; — instance types, pricing, capacity, and (per node) spot-interruption signals.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It correlates the two to decide things like &lt;em&gt;"this workload fits on a cheaper instance type"&lt;/em&gt; or &lt;em&gt;"drain this spot node before AWS reclaims it."&lt;/em&gt; Concretely, that work is spread across a handful of pods — and only one of them is the reason the hop limit matters:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;🔍 Under the hood — which CastAI component actually touches node IMDS&lt;/strong&gt;&lt;br&gt;
CastAI isn't a single pod; it installs several components into the &lt;strong&gt;&lt;code&gt;castai-agent&lt;/code&gt;&lt;/strong&gt; namespace, and only one of them is what the hop limit gates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;castai-agent&lt;/code&gt; — the read-only core agent (a &lt;strong&gt;Deployment&lt;/strong&gt;); talks to the K8s API and streams telemetry to CastAI.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;castai-cluster-controller&lt;/code&gt; — executes scaling/node actions.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;castai-evictor&lt;/code&gt;, &lt;code&gt;castai-pod-mutator&lt;/code&gt;/&lt;code&gt;pod-pinner&lt;/code&gt; — bin-packing and right-sizing.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;castai-kvisor&lt;/code&gt; — the &lt;strong&gt;security&lt;/strong&gt; agent (KSPM / runtime security; can run an eBPF DaemonSet when enabled). &lt;em&gt;(It's a DaemonSet too, so it's easy to mistake for the IMDS consumer — but it isn't the one.)&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;castai-spot-handler&lt;/code&gt; — a DaemonSet that runs on nodes and reads IMDS&lt;/strong&gt; (&lt;code&gt;/latest/meta-data/spot/instance-action&lt;/code&gt;) to detect interruption notices. &lt;strong&gt;This is the component whose per-node IMDS access the hop limit gates.&lt;/strong&gt; It's monitoring-only; it reports events, it doesn't drain nodes itself.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;p&gt;→ &lt;em&gt;So CastAI has multiple pods. Then when someone says "agent," is CastAI even a pod, or something lower-level? And where does it live in Kubernetes?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  5. "Agent" vs. pod, and why DaemonSet is the key native feature
&lt;/h2&gt;

&lt;p&gt;"Agent" is just a &lt;em&gt;role&lt;/em&gt; — background software doing a monitoring job. It's &lt;strong&gt;not&lt;/strong&gt; a special bare-metal binary; in Kubernetes it still runs as a container inside a &lt;strong&gt;pod&lt;/strong&gt;. CastAI absolutely does need pods.&lt;/p&gt;

&lt;p&gt;Two native Kubernetes workload types matter here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt; — the centralized coordinator pattern (the core &lt;code&gt;castai-agent&lt;/code&gt;). One (or a few) replicas somewhere in the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DaemonSet&lt;/strong&gt; — Kubernetes guarantees &lt;strong&gt;exactly one copy on every worker node&lt;/strong&gt;. Scale from 5 nodes to 50 and Kubernetes drops a copy onto each new node automatically. This is the standard pattern for per-node infrastructure agents (logging, security scanners, node termination handlers, and CastAI's &lt;code&gt;spot-handler&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The DaemonSet pattern is why the hop-limit issue is &lt;em&gt;per node&lt;/em&gt;: there's a spot-handler pod on each node, each one trying to reach that node's IMDS from inside a pod network namespace.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deep dive — this same pattern in AWS's own tooling&lt;/strong&gt;&lt;br&gt;
AWS's own &lt;code&gt;aws-node-termination-handler&lt;/code&gt; runs the identical shape: a small DaemonSet pod on each host polling IMDS paths like &lt;code&gt;/spot&lt;/code&gt; and &lt;code&gt;/events&lt;/code&gt;, then cordoning/draining the node when a notice appears. CastAI's spot-handler is a specialized cousin. If you understand one, you understand both.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;→ &lt;em&gt;Let's return to the hop limit with fresh eyes. If CastAI mostly talks to the Kubernetes API — and Kubernetes already knows about traffic and resource usage — why does it need node-level AWS metadata at all?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  6. Why the Kubernetes API isn't enough
&lt;/h2&gt;

&lt;p&gt;Your intuition is partly right: Kubernetes knows &lt;strong&gt;logical&lt;/strong&gt; resource facts — this pod requested 2 CPUs and 4 GB, that node is 80% full. What Kubernetes is completely &lt;strong&gt;blind&lt;/strong&gt; to is &lt;strong&gt;money and physical hardware&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes says: &lt;em&gt;"This pod needs 2 CPU / 4 GB."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;The cloud says: &lt;em&gt;"You're on an &lt;code&gt;m5.large&lt;/code&gt;, on-demand, in &lt;code&gt;us-east-1&lt;/code&gt;, costing \$X/hour — and there's a cheaper Spot option that fits."&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A cost optimizer has to join those two datasets. So it needs cloud-side facts Kubernetes can't provide.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Nuance — not &lt;em&gt;all&lt;/em&gt; of that comes from node IMDS&lt;/strong&gt;&lt;br&gt;
The tidy story "CastAI reads instance type and pricing from IMDS on every node" overstates IMDS's role. Instance-type catalogs and pricing come largely from &lt;strong&gt;cloud provider APIs and CastAI's own pricing database&lt;/strong&gt; (via the cluster-controller's IAM permissions), not from per-node IMDS calls. The thing that genuinely &lt;em&gt;must&lt;/em&gt; be read from a node's own IMDS is &lt;strong&gt;live, node-local state&lt;/strong&gt; — most importantly the &lt;strong&gt;spot-interruption notice&lt;/strong&gt; the spot-handler watches for. That's real-time, node-specific, and only available at &lt;code&gt;169.254.169.254&lt;/code&gt; on that box. So the honest phrasing is: &lt;em&gt;the hop-limit change unblocks node-local IMDS reads (spot interruption being the key one), not "CastAI's entire pricing engine."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;→ &lt;em&gt;Fine — but here's what really bugs me: normal application pods live behind the exact same container network layer. They'd follow the identical two-hop path. So why does a hop limit of 1 seem to "work" for them but not for CastAI?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  7. The question that unlocks everything: hop limit 1 doesn't "work" for normal pods either
&lt;/h2&gt;

&lt;p&gt;This is the crux, and the usual explanation gets it subtly wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A hop limit of 1 blocks &lt;em&gt;every&lt;/em&gt; pod equally&lt;/strong&gt; — CastAI's and yours. There's no per-application exception. The reason your app pods don't &lt;em&gt;complain&lt;/em&gt; is that &lt;strong&gt;modern EKS apps don't call IMDS at all.&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Instead of IMDS, well-configured app pods get AWS credentials through &lt;strong&gt;IRSA (IAM Roles for Service Accounts)&lt;/strong&gt; or the newer &lt;strong&gt;EKS Pod Identity&lt;/strong&gt;. AWS injects a short-lived token straight into the container; the SDK finds it and talks to regional AWS API endpoints — &lt;strong&gt;never touching &lt;code&gt;169.254.169.254&lt;/code&gt;.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Because they never ping IMDS, they never hit the 1-hop wall. They're blissfully unaware it exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;💡 Clarification — "normal pods don't use IMDS" is only half true&lt;/strong&gt;&lt;br&gt;
Plenty of &lt;em&gt;system&lt;/em&gt; pods absolutely do use node IMDS and &lt;strong&gt;will break at hop limit 1&lt;/strong&gt;. Real-world examples people hit constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;EBS CSI driver&lt;/strong&gt; fetches instance identity from IMDS; at hop limit 1 in some EKS setups, volumes fail to attach and pods get stuck &lt;strong&gt;Pending&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot / node-termination handlers&lt;/strong&gt; (AWS's and CastAI's) poll IMDS for interruption notices.&lt;/li&gt;
&lt;li&gt;Certain &lt;strong&gt;CNI configurations&lt;/strong&gt; need it (some overlay CNIs like Cilium even need a hop limit of &lt;strong&gt;3&lt;/strong&gt;: one for the node, one for the pod, one for the overlay).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is precisely why &lt;strong&gt;EKS managed node groups ship with hop limit 2 by default&lt;/strong&gt; — AWS knew containerized infra components need it. So the real framing isn't "CastAI is uniquely needy"; it's "CastAI's spot-handler joins the set of node agents that need IMDS, and your nodes happen to be locked at 1."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Why hop limit 1 exists at all:&lt;/strong&gt; it's a deliberate security control. If an attacker compromises a public-facing pod, a limit of 1 stops them from reaching &lt;code&gt;169.254.169.254&lt;/code&gt; to steal the &lt;strong&gt;node's&lt;/strong&gt; IAM credentials (which are powerful — see §8). It contains the blast radius to that pod.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Compromised app pod ]
      ├─► tries IMDS  (hop limit 1) ──► BLOCKED (can't steal the node's admin-ish creds)
      └─► tries IRSA  ─────────────────► works, but only its own narrow permissions
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;→ &lt;em&gt;That raises two things at once. Is IRSA-vs-IMDS a setting I flip per app? And if a hacker can still use IRSA, why is that any safer than IMDS?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  8. IRSA vs. IMDS: not a toggle, and why the attacker gains little
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is it a config switch?&lt;/strong&gt; No single "use IRSA instead of IMDS" toggle. It's an &lt;strong&gt;architecture&lt;/strong&gt; baked into the AWS SDK &lt;strong&gt;credential provider chain&lt;/strong&gt;. The SDK checks credential sources in a fixed priority order; IRSA-injected credentials sit near the top. To wire up IRSA you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a Kubernetes &lt;strong&gt;ServiceAccount&lt;/strong&gt; annotated with an &lt;strong&gt;IAM Role ARN&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Attach that ServiceAccount to the pod.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At launch, Kubernetes mounts a signed token into the container and sets an env var pointing at it. The SDK sees that token first, uses it, and never falls through to IMDS. (EKS Pod Identity achieves the same outcome with a different, newer mechanism.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why can't a hacker just use IRSA?&lt;/strong&gt; They can — but it buys them almost nothing, thanks to &lt;strong&gt;least privilege&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;strong&gt;Node IMDS credentials&lt;/strong&gt;&lt;/th&gt;
&lt;th&gt;&lt;strong&gt;IRSA / Pod Identity credentials&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scope&lt;/td&gt;
&lt;td&gt;The &lt;strong&gt;entire EC2 node's&lt;/strong&gt; IAM role&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;One pod's&lt;/strong&gt; narrowly-scoped role&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Typical power&lt;/td&gt;
&lt;td&gt;Broad: attach ENIs, pull images, modify routes, talk to the control plane&lt;/td&gt;
&lt;td&gt;Only what that app needs (e.g., one S3 bucket)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Blast radius if stolen&lt;/td&gt;
&lt;td&gt;Can manipulate broader AWS infra&lt;/td&gt;
&lt;td&gt;Limited to that app's tiny sandbox&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Stealing IMDS creds is a catastrophe; stealing one pod's IRSA creds is a contained incident. That asymmetry is the whole reason a hop limit of 1 is a sane default.&lt;/p&gt;

&lt;p&gt;→ &lt;em&gt;So we're deliberately opening that door for CastAI. What's my guarantee CastAI's own pod won't be the thing that gets popped? Is that even possible — and if not, why not?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  9. What if the CastAI pod itself is compromised?
&lt;/h2&gt;

&lt;p&gt;Honest answer: &lt;strong&gt;it's possible.&lt;/strong&gt; No software is exploit-proof, and there's no 100% guarantee. Security engineers approve this change not because breach is impossible, but because the &lt;strong&gt;blast radius is heavily constrained&lt;/strong&gt; by three overlapping controls:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;IMDSv2 is session-based&lt;/strong&gt; — no static string to copy-paste out. An attacker must do a PUT to get a short-lived token, then pass it in a header on the GET. The token expires quickly and won't work off the instance's network boundary. (This is the "v2" hardening; the hop limit and IMDSv2 together are what you're standardizing on.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CastAI's IAM role is tightly scoped&lt;/strong&gt; — infrastructure/metadata actions (describe instances, node scaling), &lt;em&gt;not&lt;/em&gt; your customer databases, app secrets, or private buckets. Even assuming CastAI's identity, an attacker inherits only CastAI's narrow permissions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Namespace isolation + Network Policies&lt;/strong&gt; — CastAI lives in its own &lt;code&gt;castai-agent&lt;/code&gt; namespace. With standard Kubernetes NetworkPolicies enforced, cross-namespace traffic can be blocked, so an attacker in the CastAI pod can't freely pivot into your app pods.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The trade-off, stated plainly:&lt;/strong&gt; raising the hop limit to 2 does widen network visibility for node agents. It's a &lt;em&gt;calculated&lt;/em&gt; risk, defended by IMDSv2 session tokens + least-privilege IAM + namespace/network isolation, in exchange for continuous automated cost optimization.&lt;/p&gt;

&lt;p&gt;→ &lt;em&gt;You keep saying "namespace." Is a Kubernetes namespace the same thing as a Linux namespace I've heard about in containers? Are they related?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Kubernetes namespace vs. Linux namespace — related, but different universes
&lt;/h2&gt;

&lt;p&gt;They share a word and are deeply connected, but they operate at &lt;strong&gt;completely different layers.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Linux namespace = the real isolation technology (kernel level).&lt;/strong&gt;&lt;br&gt;
A Linux namespace is a kernel feature that makes a process believe it's alone on the machine. Containers &lt;em&gt;are&lt;/em&gt; Linux namespaces wrapped around a process:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Network (&lt;code&gt;net&lt;/code&gt;)&lt;/strong&gt; — its own interfaces, loopback, and IP. &lt;strong&gt;← this is the extra hop.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PID&lt;/strong&gt; — the container's main process thinks it's PID 1, even though the host sees PID 48291.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mount (&lt;code&gt;mnt&lt;/code&gt;)&lt;/strong&gt; — sees only its own filesystem.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without Linux namespaces, containers wouldn't exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kubernetes namespace = an organizational label (orchestration level).&lt;/strong&gt;&lt;br&gt;
A Kubernetes namespace (&lt;code&gt;default&lt;/code&gt;, &lt;code&gt;castai-agent&lt;/code&gt;, …) is &lt;strong&gt;just a metadata tag in the Kubernetes API database (etcd)&lt;/strong&gt;. The Linux kernel has no idea it exists. It's an admin construct for grouping resources, scoping RBAC, and setting quotas — like a locked department suite inside an office building.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How they meet in this exact problem:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ EC2 Node ] ── single Linux kernel, owns the host network namespace
   │
   ├── K8s Namespace "castai-agent"   ← logical label in etcd only
   │      └── Pod: castai-spot-handler
   │             └── Linux net namespace (isolated IP)  ─┐
   │                                                     │ Hop 1 (cross the veth to host)
   └── Host network namespace (the EC2 OS) ◄─────────────┘
                                                         │ Hop 2 (host → IMDS)
                                     [ AWS IMDS 169.254.169.254 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Kubernetes provides the &lt;strong&gt;organizational wrapper&lt;/strong&gt; (the K8s namespace); the &lt;strong&gt;Linux network namespace&lt;/strong&gt; is what physically creates the hop that forces &lt;code&gt;hopLimit=2&lt;/code&gt;. Two different meanings of "namespace," both true, meeting on the same node.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deep dive — the &lt;code&gt;hostNetwork&lt;/code&gt; escape hatch&lt;/strong&gt;&lt;br&gt;
A pod can set &lt;code&gt;hostNetwork: true&lt;/code&gt;, which drops it &lt;em&gt;into the host's&lt;/em&gt; network namespace — no separate pod net namespace, so it reaches IMDS in a single hop and doesn't care about the limit. Some infra agents use this. CastAI's model keeps components in the normal pod network (so it can map workloads cleanly), which is why the node-level fix is &lt;code&gt;hopLimit=2&lt;/code&gt; rather than &lt;code&gt;hostNetwork&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;→ &lt;em&gt;Now I understand the plumbing. Operationally, how does CastAI run — is it a cron job that wakes up, checks IMDS and Kubernetes, and exits?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  11. How often does CastAI actually poll? (It's not a cron job)
&lt;/h2&gt;

&lt;p&gt;A Kubernetes &lt;strong&gt;CronJob&lt;/strong&gt; wakes on a schedule, runs to completion, and exits. CastAI can't work that way — costs, traffic, and scheduling pressure change second to second. It runs as &lt;strong&gt;long-lived daemons&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes state — real-time, event-driven.&lt;/strong&gt; CastAI uses native &lt;strong&gt;informers/watchers&lt;/strong&gt;: it holds a persistent streaming connection to the API server, which &lt;strong&gt;pushes&lt;/strong&gt; events the instant a pod becomes unschedulable or usage shifts. No fixed polling timer for cluster state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node IMDS core facts — essentially once, then cached.&lt;/strong&gt; Instance ID / type / region don't change while a node runs, so the agent reads them at startup and caches them; it only re-reads on pod restart or when a brand-new node joins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot-interruption watch — continuous, lightweight loop.&lt;/strong&gt; On spot nodes, the spot-handler keeps polling the IMDS interruption path, because AWS gives only a short notice (&lt;strong&gt;up to ~2 minutes on AWS&lt;/strong&gt;; ~30 seconds on GCP/Azure) before reclaiming the instance. CastAI additionally runs ML-based interruption &lt;em&gt;prediction&lt;/em&gt; for a longer heads-up.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Cadence&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cluster state (pods/nodes)&lt;/td&gt;
&lt;td&gt;Real-time&lt;/td&gt;
&lt;td&gt;K8s API watchers (push)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node identity (ID/type/region)&lt;/td&gt;
&lt;td&gt;Once at boot, cached&lt;/td&gt;
&lt;td&gt;IMDSv2 GET&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spot interruption notice&lt;/td&gt;
&lt;td&gt;Continuous short loop&lt;/td&gt;
&lt;td&gt;IMDS &lt;code&gt;/spot/instance-action&lt;/code&gt; on the node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cluster metrics&lt;/td&gt;
&lt;td&gt;Short interval (impl. detail)&lt;/td&gt;
&lt;td&gt;Metrics scrape&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;(The often-quoted "every 15 seconds" metrics figure is an implementation detail that varies by setup — treat the exact number as approximate.)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;→ &lt;em&gt;Last question. To actually get hop limit 2 onto our existing cluster — do we have to restart or replace nodes? Is that why the change request mentions downtime?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  12. Applying the change: why nodes get replaced (and why "potential downtime")
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes — existing nodes must be replaced (or at least relaunched) to pick up the new limit.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The hop limit is baked into the &lt;strong&gt;EC2 launch template&lt;/strong&gt; (the blueprint the EKS node group / Auto Scaling Group uses to build nodes). Editing the template from &lt;code&gt;1&lt;/code&gt; to &lt;code&gt;2&lt;/code&gt; &lt;strong&gt;does not&lt;/strong&gt; retroactively change already-running instances. New settings only apply to &lt;strong&gt;newly launched&lt;/strong&gt; nodes built from the updated template.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; on a &lt;em&gt;single&lt;/em&gt; running instance you &lt;em&gt;can&lt;/em&gt; change it live with &lt;code&gt;aws ec2 modify-instance-metadata-options --http-put-response-hop-limit 2&lt;/code&gt; (no reboot). But for a managed fleet you fix the launch template and roll the nodes, so replacements are consistent and the change survives future scale-ups.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;How the rolling update works (no big-bang reboot):&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cordon&lt;/strong&gt; — mark an old node &lt;code&gt;SchedulingDisabled&lt;/code&gt; so no &lt;em&gt;new&lt;/em&gt; pods land on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drain&lt;/strong&gt; — evict its pods; Kubernetes reschedules them onto other nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminate &amp;amp; replace&lt;/strong&gt; — once empty, delete it; the ASG launches a fresh node from the updated template, now with hop limit 2.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why the change request still warns about downtime:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Single-replica workloads&lt;/strong&gt; — a 1-pod app is briefly unavailable while it's killed on the old node and recreated on the new one. Mitigate with ≥2 replicas + a PodDisruptionBudget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capacity churn&lt;/strong&gt; — moving many pods at once can cause short blips while new nodes pass health checks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A well-architected app with multiple replicas and sane PDBs typically &lt;strong&gt;rides through the roll with zero user-visible impact&lt;/strong&gt;. The "potential downtime" language is there for the single-replica and tight-capacity cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  Putting it all together — one diagram
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                          ┌─────────────────────────────────────────────┐
                          │           EKS Node (EC2 instance)            │
                          │  Linux kernel · host network namespace       │
                          │                                              │
   K8s API  ◄──watchers───┤  [castai-agent]  (Deployment, read-only)     │
   (push events)          │        │ telemetry → CastAI cloud            │
                          │        ▼                                     │
   CastAI cloud ◄─────────┤  optimization decisions                     │
   (pricing DB,           │                                              │
    cloud APIs)           │  [castai-spot-handler]  (DaemonSet)          │
                          │        │ reads node-local IMDS               │
                          │        │  Hop 1: pod netns → host  ──────┐   │
                          │        └─────────────────────────────────┘   │
                          │                              Hop 2: host → IMDS
                          └──────────────────────────────────┬───────────┘
                                                              ▼
                                              AWS IMDS (169.254.169.254)
                                   hop limit 1 = blocked · hop limit 2 = allowed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The mental model in five lines
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Hop limit = TTL&lt;/strong&gt; on IMDS responses; a pod-to-IMDS request costs &lt;strong&gt;2 hops&lt;/strong&gt; because a pod sits in its own Linux network namespace.&lt;/li&gt;
&lt;li&gt;Limit &lt;strong&gt;1&lt;/strong&gt; is a &lt;strong&gt;security default&lt;/strong&gt; (contain node-credential theft); it blocks &lt;em&gt;all&lt;/em&gt; pods, not just CastAI.&lt;/li&gt;
&lt;li&gt;Most &lt;strong&gt;app&lt;/strong&gt; pods skip IMDS via &lt;strong&gt;IRSA / Pod Identity&lt;/strong&gt;, so they never notice — but &lt;strong&gt;node/infra agents&lt;/strong&gt; (EBS CSI, spot handlers, CastAI's spot-handler) need it and break at 1.&lt;/li&gt;
&lt;li&gt;Raising to &lt;strong&gt;2&lt;/strong&gt; is defended by &lt;strong&gt;IMDSv2 tokens + scoped IAM + namespace/network isolation&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The setting lives in the &lt;strong&gt;launch template&lt;/strong&gt;, so applying it means a &lt;strong&gt;rolling node replacement&lt;/strong&gt; — hence the downtime caveat.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The common explanations, clarified
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Default hop limit is always 1."&lt;/strong&gt; → Generic EC2 yes; but &lt;strong&gt;EKS managed node groups and Amazon Linux 2023 default to 2&lt;/strong&gt;. Nodes stuck at 1 are usually a hardened/custom (or spot/Karpenter) config.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"IMDSv2 sets the hop limit."&lt;/strong&gt; → Hop limit is an &lt;strong&gt;independent&lt;/strong&gt; metadata option; IMDSv2 is the session-token requirement. Configured together, but distinct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"The kvisor DaemonSet needs IMDS."&lt;/strong&gt; → The per-node IMDS consumer is the &lt;strong&gt;&lt;code&gt;castai-spot-handler&lt;/code&gt; DaemonSet&lt;/strong&gt; (monitoring-only). &lt;code&gt;kvisor&lt;/code&gt; is the security agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"CastAI reads instance type &amp;amp; pricing from node IMDS."&lt;/strong&gt; → Type/pricing come mainly from &lt;strong&gt;cloud APIs + CastAI's pricing DB&lt;/strong&gt;; the essential &lt;em&gt;node-local&lt;/em&gt; IMDS read is the &lt;strong&gt;spot-interruption notice&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"Normal pods don't hit the hop wall."&lt;/strong&gt; → They don't &lt;em&gt;if&lt;/em&gt; they use IRSA. But &lt;strong&gt;system pods (EBS CSI, termination handlers, some CNIs) do&lt;/strong&gt; and fail at limit 1 — the reason EKS defaults to 2.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Further reading (for your own deep dive)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;AWS — &lt;em&gt;Use the Instance Metadata Service to access instance metadata&lt;/em&gt; (hop limit / IMDSv2): &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS — &lt;em&gt;Instance metadata access considerations&lt;/em&gt; (container / hop-limit-2 guidance): &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS — &lt;em&gt;Amazon EKS now supports IMDSv2&lt;/em&gt; (why EKS node groups default to hop limit 2): &lt;a href="https://aws.amazon.com/about-aws/whats-new/2020/08/amazon-eks-supports-ec2-instance-metadata-service-v2" rel="noopener noreferrer"&gt;https://aws.amazon.com/about-aws/whats-new/2020/08/amazon-eks-supports-ec2-instance-metadata-service-v2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Amazon Linux 2023 — &lt;em&gt;IMDSv2&lt;/em&gt; (AL2023 defaults hop limit to 2): &lt;a href="https://docs.aws.amazon.com/linux/al2023/ug/imdsv2.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/linux/al2023/ug/imdsv2.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CastAI Docs — &lt;em&gt;Read-only agent&lt;/em&gt; &amp;amp; &lt;em&gt;Spot handler&lt;/em&gt;: &lt;a href="https://docs.cast.ai/docs/about-the-read-only-agent" rel="noopener noreferrer"&gt;https://docs.cast.ai/docs/about-the-read-only-agent&lt;/a&gt; and &lt;a href="https://docs.cast.ai/docs/spot-handler" rel="noopener noreferrer"&gt;https://docs.cast.ai/docs/spot-handler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS — &lt;em&gt;aws-node-termination-handler&lt;/em&gt; (the same DaemonSet-reads-IMDS pattern): &lt;a href="https://github.com/aws/aws-node-termination-handler" rel="noopener noreferrer"&gt;https://github.com/aws/aws-node-termination-handler&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes — &lt;em&gt;Namespaces&lt;/em&gt; concept: &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Linux — &lt;em&gt;namespaces(7)&lt;/em&gt; man page: &lt;a href="https://man7.org/linux/man-pages/man7/namespaces.7.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man7/namespaces.7.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS EKS — &lt;em&gt;IAM Roles for Service Accounts (IRSA)&lt;/em&gt; and &lt;em&gt;EKS Pod Identity&lt;/em&gt;: &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>kubernete</category>
    </item>
    <item>
      <title>The 'Disconnected' Dilemma: A Mac User's Journey to Connecting the Gemini CLI and Salesforce MCP server</title>
      <dc:creator>Sudhakar </dc:creator>
      <pubDate>Mon, 27 Oct 2025 04:59:10 +0000</pubDate>
      <link>https://dev.to/sudhakar6/the-disconnected-dilemma-a-mac-users-journey-to-connecting-the-gemini-cli-and-salesforce-mcp-2ack</link>
      <guid>https://dev.to/sudhakar6/the-disconnected-dilemma-a-mac-users-journey-to-connecting-the-gemini-cli-and-salesforce-mcp-2ack</guid>
      <description>&lt;h1&gt;
  
  
  🧩 The "Disconnected" Dilemma: A Mac User’s Journey to Connecting the Gemini CLI and Salesforce
&lt;/h1&gt;

&lt;p&gt;Connecting the &lt;strong&gt;Gemini CLI&lt;/strong&gt; to &lt;strong&gt;Salesforce&lt;/strong&gt; felt like unlocking a superpower — using natural language to query orgs, analyze code, and deploy metadata, all from the terminal.&lt;/p&gt;

&lt;p&gt;But what I thought would be a 10-minute setup turned into a multi-step troubleshooting puzzle.&lt;br&gt;&lt;br&gt;
The main villain?&lt;br&gt;&lt;br&gt;
🔴 &lt;strong&gt;salesforce - Disconnected&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the story of that journey, the problems I faced on &lt;strong&gt;Mac (Apple Silicon)&lt;/strong&gt;, and the “Aha!” moment that finally fixed it.&lt;br&gt;&lt;br&gt;
If you’re seeing that same red dot, this guide is for you.&lt;/p&gt;


&lt;h2&gt;
  
  
  🎯 The Goal &amp;amp; The Setup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Goal:&lt;/strong&gt; Configure the Gemini CLI to use the Salesforce MCP Server.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;The Machine:&lt;/strong&gt; A Mac with Apple Silicon (M1/M2/M3).&lt;/p&gt;
&lt;h3&gt;
  
  
  🧰 The Key Software
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemini CLI&lt;/strong&gt; – The AI agent.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Salesforce CLI (&lt;code&gt;sf&lt;/code&gt;)&lt;/strong&gt; – Installed and authorized with a default org.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;npm / npx&lt;/strong&gt; – Used to run the MCP server package.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  🧪 The Troubleshooting Timeline
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Phase 1: The “By the Book” Attempt (And the First Failure)
&lt;/h3&gt;

&lt;p&gt;I started by following the developer documentation. It suggested using the Salesforce CLI directly to start the server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My &lt;code&gt;~/.gemini/settings.json&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"salesforce"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sf"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"force"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"mcp:server:start"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Result:&lt;/strong&gt;&lt;br&gt;
I launched Gemini and ran:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
/mcp list&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Output:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
✗ salesforce: ... - Disconnected&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The command seemed logical but clearly wasn’t establishing the persistent connection Gemini needed.&lt;/p&gt;




&lt;h3&gt;
  
  
  Phase 2: Finding the Right Package (And a New Mystery)
&lt;/h3&gt;

&lt;p&gt;After more research, I found that the standard way to run the server is by using the dedicated npm package &lt;strong&gt;&lt;a class="mentioned-user" href="https://dev.to/salesforce"&gt;@salesforce&lt;/a&gt;/mcp&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My &lt;code&gt;~/.gemini/settings.json&lt;/code&gt; (Attempt 2):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "salesforce": {&lt;br&gt;
      "command": "npx",&lt;br&gt;
      "args": [&lt;br&gt;
        "-y",&lt;br&gt;
        "@salesforce/mcp@latest",&lt;br&gt;
        "--orgs",&lt;br&gt;
        "DEFAULT_TARGET_ORG",&lt;br&gt;
        "--toolsets",&lt;br&gt;
        "orgs,metadata,code-analysis"&lt;br&gt;
      ]&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Result:&lt;/strong&gt;&lt;br&gt;
After restarting Gemini and running &lt;code&gt;/mcp list&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
✗ salesforce: npx -y @salesforce/mcp ... - Disconnected&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This was frustrating. The command was correct, the package official, and the org authorized. So why was it still disconnecting?&lt;/p&gt;




&lt;h3&gt;
  
  
  Phase 3: The “Aha!” Moment (Testing the Command Directly)
&lt;/h3&gt;

&lt;p&gt;We realized we were assuming the command itself was failing.&lt;br&gt;
But what if &lt;strong&gt;Gemini&lt;/strong&gt; was the one failing to connect?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Test:&lt;/strong&gt;&lt;br&gt;
Run the command directly in the terminal:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
npx -y @salesforce/mcp@latest --orgs DEFAULT_TARGET_ORG --toolsets orgs,metadata,code-analysis&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
Salesforce MCP Server v0.23.4 running on stdio&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;✅ The server worked perfectly.&lt;br&gt;
The Salesforce CLI authorization and &lt;code&gt;npx&lt;/code&gt; command were fine.&lt;br&gt;
The issue wasn’t Salesforce — it was how Gemini was spawning the process.&lt;/p&gt;




&lt;h3&gt;
  
  
  Phase 4: The Mac Silicon Clue (Environment Mismatch)
&lt;/h3&gt;

&lt;p&gt;Here’s where the Apple Silicon clue came in.&lt;/p&gt;

&lt;p&gt;When Gemini (a Node.js app) spawns a child process (like &lt;code&gt;npx&lt;/code&gt;), it doesn’t always inherit the full &lt;code&gt;$PATH&lt;/code&gt; from your interactive shell (like &lt;code&gt;.zshrc&lt;/code&gt;).&lt;br&gt;
This happens frequently when using version managers like &lt;strong&gt;nvm&lt;/strong&gt; or &lt;strong&gt;fnm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Gemini was trying to run &lt;code&gt;npx&lt;/code&gt;, but its isolated environment couldn’t find the command.&lt;br&gt;
The process exited instantly, and Gemini reported &lt;strong&gt;Disconnected&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  Phase 5: The Fix (Absolute Path to the Rescue)
&lt;/h3&gt;

&lt;p&gt;If the &lt;code&gt;$PATH&lt;/code&gt; is unreliable, don’t rely on it.&lt;br&gt;
Tell Gemini exactly where &lt;code&gt;npx&lt;/code&gt; lives.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Find the Path&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
which npx&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Example output:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
/usr/local/bin/npx&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Yours might differ, such as &lt;code&gt;/Users/&amp;lt;your-name&amp;gt;/.nvm/versions/node/v20.x.x/bin/npx&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Update &lt;code&gt;settings.json&lt;/code&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Final, working version:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "salesforce": {&lt;br&gt;
      "command": "/usr/local/bin/npx",&lt;br&gt;
      "args": [&lt;br&gt;
        "-y",&lt;br&gt;
        "@salesforce/mcp",&lt;br&gt;
        "--orgs",&lt;br&gt;
        "DEFAULT_TARGET_ORG",&lt;br&gt;
        "--toolsets",&lt;br&gt;
        "orgs,metadata,code-analysis",&lt;br&gt;
        "--tools",&lt;br&gt;
        "deploy_metadata,retrieve_metadata"&lt;br&gt;
      ]&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Phase 6: Success!
&lt;/h3&gt;

&lt;p&gt;After saving the file and relaunching Gemini, I ran:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
/mcp list&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Output:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
✓ salesforce: /usr/local/bin/npx -y @salesforce/mcp ... (stdio) - Connected&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;It finally connected 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Now What? How to Use Your Connected Server
&lt;/h2&gt;

&lt;p&gt;Getting &lt;strong&gt;Connected&lt;/strong&gt; is just the first step.&lt;br&gt;
Next, you need to use the available tools through natural language.&lt;/p&gt;

&lt;p&gt;You don’t explicitly tell Gemini which tool to use — it infers that based on your prompt.&lt;/p&gt;




&lt;h3&gt;
  
  
  💬 How to Prompt Gemini
&lt;/h3&gt;

&lt;p&gt;Use descriptive keywords that align with the tools (&lt;code&gt;deploy_metadata&lt;/code&gt;, &lt;code&gt;retrieve_metadata&lt;/code&gt;, &lt;code&gt;code-analysis&lt;/code&gt;, etc.).&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Retrieve metadata:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Please retrieve the metadata for the Account object from my default Salesforce org.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Deploy metadata:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Take the file &lt;code&gt;src/classes/MyNewClass.cls&lt;/code&gt; and deploy it to Salesforce.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;Code analysis:&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;“Analyze the Apex class &lt;code&gt;MyController.cls&lt;/code&gt; for any security vulnerabilities or performance issues.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Each time, Gemini asks for confirmation and shows the exact command it plans to execute — proof that the MCP server is being used.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Appendix: Installation &amp;amp; Useful Links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🧩 1. Running via npx (Standard Method)
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
npx -y @salesforce/mcp@latest --orgs DEFAULT_TARGET_ORG --toolsets orgs,metadata,data,users&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;br&gt;
Downloads and runs the latest MCP package, then removes it.&lt;br&gt;
Perfect for testing.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧰 2. Installing Globally (Optional Troubleshooting Step)
&lt;/h3&gt;

&lt;p&gt;If &lt;code&gt;npx&lt;/code&gt; is unreliable, you can install globally:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
npm install -g @salesforce/mcp&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This ensures a stable global package version.&lt;br&gt;
Not required for most users, but helpful if &lt;code&gt;npx&lt;/code&gt; caching fails.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔗 Related Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;📘 &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_mcp.htm" rel="noopener noreferrer"&gt;Salesforce DX MCP Server and Tools (Beta)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 &lt;a href="https://github.com/salesforcecli/mcp?tab=readme-ov-file" rel="noopener noreferrer"&gt;Salesforce MCP Server GitHub Repository&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  💡 Key Lessons for Future Users
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"Disconnected" = "Instant Exit"&lt;/strong&gt; → The server process likely failed to start.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test Commands Directly&lt;/strong&gt; → Always run the &lt;code&gt;command&lt;/code&gt; + &lt;code&gt;args&lt;/code&gt; outside Gemini first.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mac/Linux Users (especially with nvm/fnm):&lt;/strong&gt;
Use the &lt;strong&gt;absolute path&lt;/strong&gt; to &lt;code&gt;npx&lt;/code&gt; or other executables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Windows Users:&lt;/strong&gt;
Use &lt;code&gt;"cmd"&lt;/code&gt; with &lt;code&gt;"/c"&lt;/code&gt; in &lt;code&gt;settings.json&lt;/code&gt;, e.g.:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
  "command": "cmd",&lt;br&gt;
  "args": ["/c", "npx", ...]&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;🏁 &lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;br&gt;
The “Disconnected” message wasn’t a bug — it was a clue.&lt;br&gt;
Understanding how Gemini spawns its processes revealed the real issue: environment inheritance.&lt;br&gt;
With the absolute path fix, the Gemini–Salesforce bridge now runs perfectly.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;




</description>
      <category>salesforce</category>
      <category>gemini</category>
      <category>cli</category>
      <category>macos</category>
    </item>
    <item>
      <title>Salesforce MCP Server for Gemini CLI (Mac, Linux, Windows)</title>
      <dc:creator>Sudhakar </dc:creator>
      <pubDate>Mon, 27 Oct 2025 04:52:37 +0000</pubDate>
      <link>https://dev.to/sudhakar6/salesforce-mcp-server-for-gemini-cli-mac-linux-windows-3cjm</link>
      <guid>https://dev.to/sudhakar6/salesforce-mcp-server-for-gemini-cli-mac-linux-windows-3cjm</guid>
      <description>&lt;h1&gt;
  
  
  ⚙️ Configuring the Salesforce MCP Server for Gemini CLI
&lt;/h1&gt;

&lt;h3&gt;
  
  
  🧭 Hands-On Guide for macOS, Linux, and Windows
&lt;/h3&gt;

&lt;p&gt;Connecting the &lt;strong&gt;Gemini CLI&lt;/strong&gt; to the &lt;strong&gt;Salesforce MCP Server&lt;/strong&gt; unlocks a powerful workflow — allowing you to query orgs, analyze code, and deploy metadata using natural language.&lt;/p&gt;

&lt;p&gt;However, many users — especially on &lt;strong&gt;macOS with Apple Silicon&lt;/strong&gt; — immediately encounter a frustrating&lt;br&gt;&lt;br&gt;
🔴 &lt;strong&gt;salesforce - Disconnected&lt;/strong&gt; error.&lt;/p&gt;

&lt;p&gt;This guide walks you through the correct, stable configuration to get you up and running.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Root cause: usually an environment &lt;code&gt;$PATH&lt;/code&gt; issue.&lt;br&gt;&lt;br&gt;
The fix: explicitly define the full command path Gemini should run.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  📑 Table of Contents
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
Prerequisites
&lt;/li&gt;
&lt;li&gt;
Step 1: Find Your &lt;code&gt;settings.json&lt;/code&gt; File
&lt;/li&gt;
&lt;li&gt;
Step 2: Get the Server Command (The Gotcha &amp;amp; Fix)

&lt;ul&gt;
&lt;li&gt;
macOS &amp;amp; Linux Users
&lt;/li&gt;
&lt;li&gt;
Windows Users
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Step 3: Configure Your &lt;code&gt;settings.json&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;
macOS / Linux Configuration
&lt;/li&gt;
&lt;li&gt;
Windows Configuration
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
Step 4: Verify the Connection
&lt;/li&gt;
&lt;li&gt;
Step 5: Use the Connected Server
&lt;/li&gt;
&lt;li&gt;Appendix: Installation &amp;amp; Useful Links&lt;/li&gt;
&lt;/ol&gt;


&lt;h2&gt;
  
  
  🧰 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before you begin, ensure you have the following installed and configured:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gemini CLI&lt;/strong&gt; – Google’s AI agent for your terminal
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Salesforce CLI (&lt;code&gt;sf&lt;/code&gt;)&lt;/strong&gt; – The standard Salesforce command-line tool
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js / npm / npx&lt;/strong&gt; – The Node.js runtime and package manager
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authorized Salesforce Org&lt;/strong&gt; – You must have an org authorized and set as default in Salesforce CLI
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Authorize an org:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sf org login web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Or set an existing org as default:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
sf config set target-org &amp;lt;your-org-alias&amp;gt;&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🏠 Step 1: Find Your &lt;code&gt;settings.json&lt;/code&gt; File
&lt;/h2&gt;

&lt;p&gt;Gemini CLI uses a configuration file named &lt;code&gt;settings.json&lt;/code&gt;, located in your home directory.&lt;br&gt;
This file tells Gemini how to start the Salesforce server.&lt;/p&gt;

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

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
~/.gemini/settings.json&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If this file or directory doesn’t exist, create them manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧩 Step 2: Get the Server Command (The Gotcha &amp;amp; Fix)
&lt;/h2&gt;

&lt;p&gt;When Gemini starts an MCP server, it launches a &lt;strong&gt;child process&lt;/strong&gt;.&lt;br&gt;
This process might &lt;strong&gt;not inherit your shell’s full &lt;code&gt;$PATH&lt;/code&gt;&lt;/strong&gt;, especially on macOS/Linux when using Node version managers like &lt;strong&gt;nvm&lt;/strong&gt; or &lt;strong&gt;fnm&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your &lt;code&gt;settings.json&lt;/code&gt; command is simply &lt;code&gt;"npx"&lt;/code&gt;, Gemini won’t find it — causing the &lt;strong&gt;Disconnected&lt;/strong&gt; error.&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Fix:&lt;/strong&gt; provide the &lt;strong&gt;absolute path&lt;/strong&gt; to your &lt;code&gt;npx&lt;/code&gt; executable.&lt;/p&gt;




&lt;h3&gt;
  
  
  For macOS &amp;amp; Linux Users
&lt;/h3&gt;

&lt;p&gt;Run this command &lt;strong&gt;outside Gemini&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
which npx&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Example outputs:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
/usr/local/bin/npx&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;or&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
/Users/your-name/.nvm/versions/node/v20.x.x/bin/npx&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Use this exact path in your configuration.&lt;/p&gt;




&lt;h3&gt;
  
  
  For Windows Users
&lt;/h3&gt;

&lt;p&gt;On Windows, &lt;code&gt;npx&lt;/code&gt; must be invoked through the command interpreter.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Command:&lt;/strong&gt; &lt;code&gt;"cmd"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First argument:&lt;/strong&gt; &lt;code&gt;"/c"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧾 Step 3: Configure Your &lt;code&gt;settings.json&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Open the file:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
~/.gemini/settings.json&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then add the &lt;code&gt;mcpServers&lt;/code&gt; block according to your OS.&lt;/p&gt;




&lt;h3&gt;
  
  
  💻 Final Configuration for macOS / Linux
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "salesforce": {&lt;br&gt;
      "command": "/usr/local/bin/npx",&lt;br&gt;
      "args": [&lt;br&gt;
        "-y",&lt;br&gt;
        "@salesforce/mcp@latest",&lt;br&gt;
        "--orgs",&lt;br&gt;
        "DEFAULT_TARGET_ORG",&lt;br&gt;
        "--toolsets",&lt;br&gt;
        "orgs,metadata,code-analysis",&lt;br&gt;
        "--tools",&lt;br&gt;
        "deploy_metadata,retrieve_metadata"&lt;br&gt;
      ]&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 Replace &lt;code&gt;"DEFAULT_TARGET_ORG"&lt;/code&gt; with your Salesforce org alias (e.g., &lt;code&gt;"my-sandbox-alias"&lt;/code&gt;).&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h3&gt;
  
  
  🪟 Final Configuration for Windows
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;json&lt;br&gt;
{&lt;br&gt;
  "mcpServers": {&lt;br&gt;
    "salesforce": {&lt;br&gt;
      "command": "cmd",&lt;br&gt;
      "args": [&lt;br&gt;
        "/c",&lt;br&gt;
        "npx",&lt;br&gt;
        "-y",&lt;br&gt;
        "@salesforce/mcp@latest",&lt;br&gt;
        "--orgs",&lt;br&gt;
        "DEFAULT_TARGET_ORG",&lt;br&gt;
        "--toolsets",&lt;br&gt;
        "orgs,metadata,code-analysis",&lt;br&gt;
        "--tools",&lt;br&gt;
        "deploy_metadata,retrieve_metadata"&lt;br&gt;
      ]&lt;br&gt;
    }&lt;br&gt;
  }&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Step 4: Verify the Connection
&lt;/h2&gt;

&lt;p&gt;Save your configuration and restart Gemini CLI.&lt;br&gt;
Then run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
/mcp list&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Expected output:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;&lt;br&gt;
✓ salesforce: /usr/local/bin/npx -y @salesforce/mcp ... (stdio) - Connected&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Step 5: How to Use Your Connected Server
&lt;/h2&gt;

&lt;p&gt;You don’t need to explicitly tell Gemini to “use the Salesforce server.”&lt;br&gt;
Just write prompts describing what you want done — Gemini automatically routes them to the right MCP tools.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example Prompts
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Retrieve Metadata&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Please retrieve the metadata for the Account object from my default Salesforce org.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Deploy Metadata&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Take the file &lt;code&gt;src/classes/MyNewClass.cls&lt;/code&gt; and deploy it to Salesforce.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Code Analysis&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Analyze the Apex class &lt;code&gt;MyController.cls&lt;/code&gt; for any security vulnerabilities or performance issues.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;💬 When you run such prompts, Gemini will &lt;strong&gt;ask for confirmation&lt;/strong&gt; before executing — confirming your MCP server is working correctly.&lt;/p&gt;




&lt;h2&gt;
  
  
  📚 Appendix: Installation &amp;amp; Useful Links
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How to Install the Salesforce MCP Server
&lt;/h3&gt;

&lt;p&gt;The setup uses &lt;code&gt;npx&lt;/code&gt; to dynamically fetch and run the MCP package.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Standard method:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
npx -y @salesforce/mcp ...&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downloads the latest &lt;code&gt;@salesforce/mcp&lt;/code&gt; package&lt;/li&gt;
&lt;li&gt;Runs it with your arguments (&lt;code&gt;--orgs&lt;/code&gt;, &lt;code&gt;--toolsets&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Removes it after execution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Optional (for troubleshooting):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;&lt;/code&gt;&lt;code&gt;bash&lt;br&gt;
npm install -g @salesforce/mcp&lt;br&gt;
&lt;/code&gt;&lt;code&gt;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Installing globally may help if &lt;code&gt;npx&lt;/code&gt; fails to find or cache the package properly.&lt;/p&gt;




&lt;h3&gt;
  
  
  🔗 Official Documentation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🧭 &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_mcp.htm" rel="noopener noreferrer"&gt;Salesforce DX MCP Server and Tools (Beta)&lt;/a&gt;&lt;br&gt;
Official developer guide describing MCP server functionality and tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;💻 &lt;a href="https://github.com/salesforcecli/mcp?tab=readme-ov-file" rel="noopener noreferrer"&gt;Salesforce MCP Server GitHub Repository&lt;/a&gt;&lt;br&gt;
Source code and community issue tracker.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;blockquote&gt;
&lt;p&gt;🏁 &lt;strong&gt;You’re all set!&lt;/strong&gt;&lt;br&gt;
Your Gemini CLI is now successfully connected to the Salesforce MCP Server — ready to analyze, query, and deploy with AI assistance.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>salesforce</category>
      <category>gemini</category>
      <category>cli</category>
      <category>devtools</category>
    </item>
    <item>
      <title>Syncing two Git Repos (By using below commands you can move contents of one git repo to another without losing history and tags)</title>
      <dc:creator>Sudhakar </dc:creator>
      <pubDate>Sat, 10 Aug 2019 17:32:04 +0000</pubDate>
      <link>https://dev.to/sudhakar6/syncing-two-git-repos-1lhh</link>
      <guid>https://dev.to/sudhakar6/syncing-two-git-repos-1lhh</guid>
      <description>&lt;p&gt;&lt;strong&gt;Cloning SourceRepo and Pushing that to TargetRepo&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;git clone --mirror SourceRepoURL&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The above git clone command will create a directory in your local machine with the name of your repo.&lt;/p&gt;

&lt;p&gt;Change to that directory and add a remote to the target repo.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;cd SourceRepo&lt;br&gt;
git remote add NEW-REMOTE TargetRepoURL&lt;/em&gt;&lt;br&gt;
It creates a connection with TargetRepo.&lt;br&gt;
Here'NEW-REMOTE' is just a name, you can give your own Name. Remember that name to sync these two repos.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;git push NEW-REMOTE --mirror&lt;/em&gt;&lt;br&gt;
It will push all folders and history of Source repo to Target Repo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Syncing Those two Repos&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;After cloning source and pushing to target if you make any more  changes to source simply use below two commands from the same directory to sync.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;git fetch origin&lt;/em&gt;&lt;br&gt;
To pull SourceRepo changes&lt;/p&gt;

&lt;p&gt;&lt;em&gt;git push NEW-REMOTE --all&lt;/em&gt;&lt;br&gt;
To push(sync) SourceRepo changes to TargetRepo.&lt;/p&gt;

</description>
      <category>git</category>
    </item>
  </channel>
</rss>
