<?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: Pravesh Sudha</title>
    <description>The latest articles on DEV Community by Pravesh Sudha (@pravesh_sudha_3c2b0c2b5e0).</description>
    <link>https://dev.to/pravesh_sudha_3c2b0c2b5e0</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%2F1805492%2F62f81727-4167-4aa7-9890-40fa2d850525.png</url>
      <title>DEV Community: Pravesh Sudha</title>
      <link>https://dev.to/pravesh_sudha_3c2b0c2b5e0</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pravesh_sudha_3c2b0c2b5e0"/>
    <language>en</language>
    <item>
      <title>🌟 The CI Gate Rejected the Terraform Change—but the LLM Still Ran</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Wed, 02 Sep 2026 16:57:13 +0000</pubDate>
      <link>https://dev.to/pravesh_sudha_3c2b0c2b5e0/the-ci-gate-rejected-the-terraform-change-but-the-llm-still-ran-3hfg</link>
      <guid>https://dev.to/pravesh_sudha_3c2b0c2b5e0/the-ci-gate-rejected-the-terraform-change-but-the-llm-still-ran-3hfg</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;How I added a deterministic trace contract to an AI Terraform reviewer so rejected infrastructure changes stop before the model is invoked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I recently built an &lt;a href="https://github.com/Pravesh-Sudha/ai-devops-agent/tree/main/terraform-review-agent" rel="noopener noreferrer"&gt;AI-powered Terraform review agent&lt;/a&gt; that combines Terrascan, GitHub Actions, AWS Lambda and Gemini.&lt;/p&gt;

&lt;p&gt;The workflow is straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A pull request changes Terraform code.&lt;/li&gt;
&lt;li&gt;GitHub Actions runs Terrascan.&lt;/li&gt;
&lt;li&gt;The scan report is sent to an AWS Lambda function.&lt;/li&gt;
&lt;li&gt;Gemini reviews the findings.&lt;/li&gt;
&lt;li&gt;The pipeline accepts or rejects the change.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project worked. A risky change produced &lt;code&gt;REJECT&lt;/code&gt;, and GitHub Actions failed the pull request as expected.&lt;/p&gt;

&lt;p&gt;But while reviewing the execution path, I noticed an important problem: &lt;strong&gt;the correct final verdict did not prove that the control was enforced at the correct boundary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The pipeline rejected the change—but the LLM had already run.&lt;/p&gt;

&lt;p&gt;That distinction matters in DevOps. A policy that says “do not continue” should stop the next action. It should not merely ask the next action to agree that the request should have been stopped.&lt;/p&gt;

&lt;p&gt;This article shows how I used AgentInspect to make that path visible and add a deterministic CI contract around it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Disclosure:&lt;/strong&gt; I tested AgentInspect independently in the workflow described here. The maintainer reviewed the AgentInspect commands for technical accuracy; the conclusions are my own. AgentInspect did not replace Terrascan, GitHub Actions, AWS controls or the application’s security policy.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The original review architecture
&lt;/h2&gt;

&lt;p&gt;My Terraform review project uses a practical serverless workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform pull request
        ↓
GitHub Actions
        ↓
Terrascan JSON report
        ↓
AWS Lambda
        ↓
Gemini review
        ↓
APPROVE | APPROVE_WITH_CHANGES | REJECT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Lambda function extracts relevant Terrascan findings and sends a bounded structure to Gemini. The prompt contains explicit decision rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reject when a HIGH or CRITICAL issue exists.&lt;/li&gt;
&lt;li&gt;Reject when there are four or more MEDIUM issues.&lt;/li&gt;
&lt;li&gt;Reject when the Application Load Balancer has no HTTPS listener.&lt;/li&gt;
&lt;li&gt;Approve with changes for one to three MEDIUM issues.&lt;/li&gt;
&lt;li&gt;Approve when only LOW or INFO issues remain.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/Pravesh-Sudha/ai-devops-agent/blob/main/.github/workflows/main.yml" rel="noopener noreferrer"&gt;GitHub Actions workflow&lt;/a&gt; then reads the returned verdict and exits with status &lt;code&gt;1&lt;/code&gt; when the model returns &lt;code&gt;REJECT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;At first, this looked like a security gate. The result was correct and the PR was blocked.&lt;/p&gt;

&lt;p&gt;The problem was where the authority lived.&lt;/p&gt;

&lt;p&gt;In the original &lt;a href="https://github.com/Pravesh-Sudha/ai-devops-agent/blob/main/terraform-review-agent/lambda/lambda_function.py" rel="noopener noreferrer"&gt;Lambda implementation&lt;/a&gt;, the sequence was effectively:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;findings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_relevant_findings&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;ai_review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;call_gemini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;extract_verdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ai_review&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The risk thresholds were described inside the prompt. They were not evaluated as a deterministic control before the provider call.&lt;/p&gt;

&lt;p&gt;This creates three different concerns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An obvious rejection still consumes a model call.&lt;/li&gt;
&lt;li&gt;Prompt behavior can change even when the policy has not changed.&lt;/li&gt;
&lt;li&gt;A correct final verdict can hide an incorrect execution path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The third concern is the easiest one to miss in ordinary CI output.&lt;/p&gt;




&lt;h2&gt;
  
  
  A passing test was not enough
&lt;/h2&gt;

&lt;p&gt;Imagine a fixture containing a HIGH-severity public-access violation. A conventional test might assert:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That assertion passes whether the pipeline rejects before Gemini or calls Gemini and then accepts its rejection.&lt;/p&gt;

&lt;p&gt;Those paths are not equivalent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Desired
Terrascan → deterministic policy → REJECT → stop

Original
Terrascan → Gemini → parse response → REJECT → stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final value is the same. The control boundary is different.&lt;/p&gt;

&lt;p&gt;For the blocked test case, I wanted to assert a stronger invariant:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Terrascan and the deterministic policy check must execute, and the LLM path must execute zero times.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is an execution contract, not an answer-quality evaluation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding a small AgentInspect wrapper
&lt;/h2&gt;

&lt;p&gt;The existing Lambda is written in Python, while AgentInspect is a TypeScript-first toolkit. I did not pretend that it could automatically instrument the Python function.&lt;/p&gt;

&lt;p&gt;Instead, I added a small Node.js evidence runner at the CI boundary. It wraps the operations the pipeline owns: reading the scan, evaluating the policy and, only when appropriate, invoking the existing Lambda.&lt;/p&gt;

&lt;p&gt;For this test, I used AgentInspect &lt;code&gt;6.17.4&lt;/code&gt; and pinned the version so the CI behavior would not move underneath the experiment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;agent-inspect@6.17.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The simplified runner looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;inspectRun&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;agent-inspect&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;traceDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.agent-inspect/terraform-review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;evaluatePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;violations&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;severities&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;violations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;item&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toUpperCase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;highOrCritical&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;severities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HIGH&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;CRITICAL&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mediumCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;severities&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;severity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;severity&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;MEDIUM&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;highOrCritical&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;mediumCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;risk-threshold&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mediumCount&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;APPROVE_WITH_CHANGES&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;medium-findings&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;APPROVE&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;low-or-info-only&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;inspectRun&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;terraform-ai-review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;terrascan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;readTerrascanReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;terrascan_report.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;evaluate_policy&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;evaluatePolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;REJECT&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;review&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nf"&gt;invokeTerraformReviewLambda&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;report&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;review&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;verdict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;verdict&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;traceDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;silent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CI&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;true&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;terraform-ai-review&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fixture&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;high-severity-public-access&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example deliberately keeps the policy small. The HTTPS rule needs its own structured input or a stable mapping from specific Terrascan findings. I would not implement it by searching arbitrary free text and call that deterministic.&lt;/p&gt;

&lt;p&gt;The important change is architectural: &lt;strong&gt;code owns the risk threshold and final CI authority; the model can provide explanation and remediation only after the deterministic gate allows that path.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The trace exposed the difference
&lt;/h2&gt;

&lt;p&gt;I ran a controlled fixture representing a HIGH-severity finding and inspected the local trace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agent-inspect list &lt;span class="nt"&gt;--dir&lt;/span&gt; .agent-inspect/terraform-review
npx agent-inspect view &amp;lt;run-id&amp;gt; &lt;span class="nt"&gt;--dir&lt;/span&gt; .agent-inspect/terraform-review
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before the short-circuit, the execution contained an LLM step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform-ai-review
├─ tool:terrascan              success
└─ llm:gemini-2.5-flash       success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After moving policy enforcement ahead of the provider call, the rejected path became:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform-ai-review
├─ tool:terrascan              success
└─ tool:evaluate_policy        success
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The absence of the LLM step was now visible, but I did not want reviewers to verify it manually on every pull request. The next step was turning it into a deterministic check.&lt;/p&gt;




&lt;h2&gt;
  
  
  Defining the blocked-path contract
&lt;/h2&gt;

&lt;p&gt;AgentInspect supports deterministic checks over retained local traces. For this case, I used a JSON check configuration:&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;"checks"&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;"tool"&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;"required"&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;"terrascan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"evaluate_policy"&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;"llm"&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;"maxCalls"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;Then CI checks the trace produced by the blocked fixture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx agent-inspect check .agent-inspect/terraform-review &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; blocked-path.check.json &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--require-completed&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The broken path fails because the model call count is greater than zero. The fixed path passes because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The run completed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terrascan&lt;/code&gt; appeared.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;evaluate_policy&lt;/code&gt; appeared.&lt;/li&gt;
&lt;li&gt;No LLM step appeared.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the exact behavior I wanted from CI. It does not ask another model whether the trace looks safe. It evaluates a small, reproducible structural contract and returns a deterministic exit code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Keeping evidence with the failed build
&lt;/h2&gt;

&lt;p&gt;A CI failure without usable evidence usually starts another debugging cycle. To retain a bounded report, I added an artifact step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Run blocked-path regression&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;node scripts/run-blocked-policy-case.mjs&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Check blocked-path contract&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;npx agent-inspect check .agent-inspect/terraform-review \&lt;/span&gt;
      &lt;span class="s"&gt;--config blocked-path.check.json \&lt;/span&gt;
      &lt;span class="s"&gt;--require-completed \&lt;/span&gt;
      &lt;span class="s"&gt;--json &amp;gt; trace-contract-result.json&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build safe trace artifacts&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;npx agent-inspect artifacts .agent-inspect/terraform-review \&lt;/span&gt;
      &lt;span class="s"&gt;--output-dir ./agent-inspect-artifacts \&lt;/span&gt;
      &lt;span class="s"&gt;--github-summary "$GITHUB_STEP_SUMMARY"&lt;/span&gt;

&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload review evidence&lt;/span&gt;
  &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always()&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-agent-trace&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
      &lt;span class="s"&gt;agent-inspect-artifacts/&lt;/span&gt;
      &lt;span class="s"&gt;trace-contract-result.json&lt;/span&gt;
    &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AgentInspect creates the local report; GitHub Actions owns the upload and retention. For real pipeline data, I would still review the exact derived artifact before sharing it outside the repository. Redaction and safety scans are safeguards, not compliance certification.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed in my mental model
&lt;/h2&gt;

&lt;p&gt;My first implementation treated the final verdict as proof that the gate worked. It was only proof that the workflow ended with the expected string.&lt;/p&gt;

&lt;p&gt;The stronger DevOps questions are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Was the declared control actually executed?&lt;/li&gt;
&lt;li&gt;Did it execute before the model or side effect?&lt;/li&gt;
&lt;li&gt;Did a rejected path stop immediately?&lt;/li&gt;
&lt;li&gt;Can CI prove those facts without another probabilistic judgment?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where execution traces are useful. They provide evidence about the path rather than only the answer.&lt;/p&gt;

&lt;p&gt;There is also a useful separation of responsibilities:&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;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Terrascan&lt;/td&gt;
&lt;td&gt;Detect infrastructure findings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deterministic policy code&lt;/td&gt;
&lt;td&gt;Enforce explicit risk thresholds&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gemini&lt;/td&gt;
&lt;td&gt;Explain findings and suggest remediation on allowed paths&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AgentInspect&lt;/td&gt;
&lt;td&gt;Record and check the execution path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub Actions&lt;/td&gt;
&lt;td&gt;Enforce the build result and retain reviewed artifacts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;AgentInspect did not make the Terraform deployment secure. It helped me verify whether my own control flow matched the policy I claimed to enforce.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this check does not replace
&lt;/h2&gt;

&lt;p&gt;A green trace contract is narrow evidence. It does not prove that every Terraform rule is correct or that the deployed infrastructure is safe.&lt;/p&gt;

&lt;p&gt;This workflow still needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terrascan or another infrastructure scanner.&lt;/li&gt;
&lt;li&gt;IAM least privilege and protected deployment credentials.&lt;/li&gt;
&lt;li&gt;Branch protection and human review.&lt;/li&gt;
&lt;li&gt;Tests for malformed and incomplete scan reports.&lt;/li&gt;
&lt;li&gt;Provider timeouts, budgets and rate limits.&lt;/li&gt;
&lt;li&gt;Prompt-injection and adversarial testing for the explanatory model path.&lt;/li&gt;
&lt;li&gt;Production monitoring and incident controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It also does not prove that Gemini’s remediation advice is good. That requires separate evaluation.&lt;/p&gt;

&lt;p&gt;The contract proves one specific invariant: &lt;strong&gt;when deterministic policy rejects a Terraform change, the model path does not run.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Final takeaway
&lt;/h2&gt;

&lt;p&gt;AI can add useful context to DevOps workflows, but it should not own controls that can be expressed clearly in code.&lt;/p&gt;

&lt;p&gt;My Terraform review agent already produced the expected rejection. AgentInspect showed me that the route to that answer was weaker than the answer itself suggested.&lt;/p&gt;

&lt;p&gt;Moving the risk threshold ahead of the LLM call gave the workflow a cleaner authority boundary. Adding a trace contract made that boundary reviewable in CI.&lt;/p&gt;

&lt;p&gt;The lesson is simple:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Do not test only what your agent returned. Test which actions it was allowed to take before returning it.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Project links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/Pravesh-Sudha/ai-devops-agent" rel="noopener noreferrer"&gt;AI DevOps Agent repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/Pravesh-Sudha/ai-devops-agent/tree/main/terraform-review-agent" rel="noopener noreferrer"&gt;AI Terraform Review Agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rajudandigam/agent-inspect" rel="noopener noreferrer"&gt;AgentInspect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rajudandigam/agent-inspect/blob/main/docs/TRACE-CONTRACTS.md" rel="noopener noreferrer"&gt;Trace contracts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/rajudandigam/agent-inspect/blob/main/docs/CI-ARTIFACTS.md" rel="noopener noreferrer"&gt;CI artifact workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>cicd</category>
      <category>ai</category>
      <category>terraform</category>
    </item>
    <item>
      <title>🧐 ECS Express Mode vs Traditional ECS: A Hands-on Comparison with Terraform</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Sun, 23 Aug 2026 19:05:08 +0000</pubDate>
      <link>https://dev.to/aws-builders/ecs-express-mode-vs-traditional-ecs-a-hands-on-comparison-with-terraform-cnp</link>
      <guid>https://dev.to/aws-builders/ecs-express-mode-vs-traditional-ecs-a-hands-on-comparison-with-terraform-cnp</guid>
      <description>&lt;h2&gt;
  
  
  Hola Amigos 👋
&lt;/h2&gt;

&lt;p&gt;Welcome to the world of Cloud and Automation!&lt;/p&gt;

&lt;p&gt;AWS launched &lt;strong&gt;ECS Express Mode&lt;/strong&gt; in late 2025, and in today’s blog, we will understand how it compares to traditional AWS ECS.&lt;/p&gt;

&lt;p&gt;We will use a basic Flask portfolio website with three versions to illustrate how both approaches handle infrastructure operations with Terraform.&lt;/p&gt;




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

&lt;p&gt;Before we start, make sure you have the following requirements met on your system:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terraform:&lt;/strong&gt; Installed and configured with an AWS IAM account that has &lt;code&gt;AdministratorAccess&lt;/code&gt;. This is just for the sake of the blog and is &lt;strong&gt;not recommended in a production environment&lt;/strong&gt;. Always follow the principle of least privilege.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic understanding of Python and Docker&lt;/strong&gt;, along with AWS ECS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the above requirements are met, we are good to go with our project!&lt;/p&gt;




&lt;h1&gt;
  
  
  What is ECS Express Mode?
&lt;/h1&gt;

&lt;p&gt;On &lt;strong&gt;November 21, 2025&lt;/strong&gt;, AWS introduced ECS Express Mode, allowing developers to launch containerized applications with secure HTTPS endpoints within minutes.&lt;/p&gt;

&lt;p&gt;It is similar to traditional ECS, but without a lot of the manual configuration required for things like load balancers, target groups, scaling policies, and more.&lt;/p&gt;

&lt;p&gt;By providing your container image and an infrastructure role, you can create a complete ECS Fargate-based deployment.&lt;/p&gt;

&lt;p&gt;Express Mode automatically creates resources such as a Virtual Private Cloud (VPC), subnets, security groups, a load balancer with listener rules, HTTPS configuration, auto-scaling policies, metrics, alarms, and health checks.&lt;/p&gt;

&lt;p&gt;The best part is that &lt;strong&gt;there is no additional charge for using ECS Express Mode itself&lt;/strong&gt;. You pay for the AWS resources that are provisioned and used by your application.&lt;/p&gt;

&lt;p&gt;Another interesting feature is that a single Application Load Balancer can be shared by up to &lt;strong&gt;25 Express Mode services&lt;/strong&gt;, which can help reduce the cost of running multiple services.&lt;/p&gt;




&lt;h1&gt;
  
  
  Practical Demonstration
&lt;/h1&gt;

&lt;p&gt;We are going to use a Flask portfolio website with three versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;v1 — Foundation&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v2 — Advanced&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;v3 — Production&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will deploy the application using both a traditional ECS architecture and ECS Express Mode and see the difference between them.&lt;/p&gt;

&lt;p&gt;To get started, clone the repository using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Pravesh-Sudha/terra-projects.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate inside the &lt;code&gt;ecs-express&lt;/code&gt; directory. Inside it, you will find two directories:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ecs-express/
├── traditional/
└── express/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's start with the traditional approach.&lt;/p&gt;




&lt;h1&gt;
  
  
  Traditional ECS
&lt;/h1&gt;

&lt;p&gt;Navigate inside the &lt;code&gt;traditional&lt;/code&gt; directory. Here, you will find a bunch of Terraform configurations for ECS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;main.tf&lt;/code&gt;&lt;/strong&gt; — Specifies AWS as the cloud provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;values.tf&lt;/code&gt;&lt;/strong&gt; — Gets the default VPC and its subnets, along with a security group with port &lt;code&gt;5000&lt;/code&gt; open. Our Flask portfolio runs on this port.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ecs.tf&lt;/code&gt;&lt;/strong&gt; — Creates our ECS cluster, required IAM roles and policy attachments, task definition, and ECS service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;get_ip.sh&lt;/code&gt;&lt;/strong&gt; — A shell script that uses AWS CLI commands to retrieve the public IP of the task's network interface and generates the URL with port &lt;code&gt;5000&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One thing to note here is that with the traditional approach, we have already created quite a few resources, but we still have only a &lt;strong&gt;bare-minimum infrastructure&lt;/strong&gt; rather than a recommended production-grade setup.&lt;/p&gt;

&lt;p&gt;For example, we haven't configured things like an Application Load Balancer, HTTPS, production-grade monitoring, alarms, or scaling policies.&lt;/p&gt;

&lt;h3&gt;
  
  
  A note about architecture
&lt;/h3&gt;

&lt;p&gt;For this project, my Docker image is built for the &lt;strong&gt;AMD64 architecture&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If you want to try another Docker image, make sure it is also built for AMD64. If you are building the image on an ARM64 machine, such as an Apple Silicon Mac, you can build it for AMD64 by specifying the target platform in your Docker build command.&lt;/p&gt;

&lt;p&gt;If you want to use an ARM64 image instead, you will need to make sure the ECS task configuration supports that architecture. Getting ARM64 working with ECS Express Mode can be more complicated depending on the configuration, so for this demonstration, I am keeping things simple and using AMD64.&lt;/p&gt;




&lt;h2&gt;
  
  
  Deploying Traditional ECS
&lt;/h2&gt;

&lt;p&gt;To apply the configuration, navigate inside the directory and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ecs-express/traditional

terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fituqgwmpvlpndifk4duc.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%2Fituqgwmpvlpndifk4duc.png" alt=" " width="800" height="272"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After a minute or two, the infrastructure should be up and running.&lt;/p&gt;

&lt;p&gt;To get the URL of the website, use the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod &lt;/span&gt;u+x get_ip.sh
./get_ip.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Figax9oci26tltkqrj0lg.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%2Figax9oci26tltkqrj0lg.png" alt=" " width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the URL in your browser, and you should see the application up and running.&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%2Fff3c1zhjkgyvc612w0ul.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%2Fff3c1zhjkgyvc612w0ul.png" alt=" " width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Updating the Application Version
&lt;/h2&gt;

&lt;p&gt;Now imagine you want to update your application to a newer version.&lt;/p&gt;

&lt;p&gt;We can do this simply by updating the Docker image version inside the ECS task definition.&lt;/p&gt;

&lt;p&gt;For this demonstration, I initially deployed v2 and then switched the application back to v1. This was actually a small mistake while capturing the screenshots, but it also demonstrates something useful: the infrastructure doesn't really care whether you're moving forward or backward between image tags — Terraform will deploy the version you specify.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pravesh2003/flask-portfolio:v1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fcbq6lsgxwyi94hhsml1k.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%2Fcbq6lsgxwyi94hhsml1k.png" alt=" " width="799" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now apply the configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;--auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the deployment is completed, use the shell script again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./get_ip.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fidmppdk0jaovy5bgsaq3.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%2Fidmppdk0jaovy5bgsaq3.png" alt=" " width="800" height="483"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Head over to the URL, and you should see the updated version of your application.&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%2Ffrchh5k7yu4x9tqfok8m.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%2Ffrchh5k7yu4x9tqfok8m.png" alt=" " width="800" height="624"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  ECS Express Mode
&lt;/h1&gt;

&lt;p&gt;With the traditional approach, we created a bare-minimum infrastructure for hosting our application.&lt;/p&gt;

&lt;p&gt;And even after writing quite a bit of Terraform code, we are still missing production-oriented features such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Metrics&lt;/li&gt;
&lt;li&gt;Alarms&lt;/li&gt;
&lt;li&gt;Scaling policies&lt;/li&gt;
&lt;li&gt;Application Load Balancer&lt;/li&gt;
&lt;li&gt;Secure HTTPS connection&lt;/li&gt;
&lt;li&gt;Target groups&lt;/li&gt;
&lt;li&gt;Health checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But this is where &lt;strong&gt;ECS Express Mode&lt;/strong&gt; comes in.&lt;/p&gt;

&lt;p&gt;To see it in action, head over to the &lt;code&gt;express&lt;/code&gt; directory.&lt;/p&gt;

&lt;p&gt;You will find the following Terraform configurations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;provider.tf&lt;/code&gt;&lt;/strong&gt; — Specifies that we are using AWS as our cloud provider.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;express.tf&lt;/code&gt;&lt;/strong&gt; — Contains the &lt;code&gt;aws_ecs_express_gateway_service&lt;/code&gt; resource. This is where most of the Express Mode configuration happens.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;vars.tf&lt;/code&gt;&lt;/strong&gt; — Contains variables such as the default VPC and its subnets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;iam.tf&lt;/code&gt;&lt;/strong&gt; — Contains the execution role and infrastructure role required by Express Mode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;outputs.tf&lt;/code&gt;&lt;/strong&gt; — Provides useful outputs, including the application URL.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Deploying ECS Express Mode
&lt;/h2&gt;

&lt;p&gt;To apply the configuration, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ecs-express/express

terraform init
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fhibe32jr7oc7g2fqhji1.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%2Fhibe32jr7oc7g2fqhji1.png" alt=" " width="800" height="502"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This deployment takes a little longer than the traditional deployment. In my case, it took around &lt;strong&gt;8–10 minutes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Once the deployment is complete, Terraform will provide the URL where the application is live.&lt;/p&gt;

&lt;p&gt;Open it in your browser, and you should see the same Flask portfolio application running through ECS Express Mode.&lt;/p&gt;




&lt;h2&gt;
  
  
  Updating the Application Version
&lt;/h2&gt;

&lt;p&gt;Just like with traditional ECS, we can update the application without rebuilding the infrastructure.&lt;/p&gt;

&lt;p&gt;Go to &lt;code&gt;express.tf&lt;/code&gt; and change the Docker image version.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"pravesh2003/flask-portfolio:v3"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then simply run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fct76ohgwgi9y3a2ibwjt.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%2Fct76ohgwgi9y3a2ibwjt.png" alt=" " width="800" height="482"&gt;&lt;/a&gt;&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%2Fz8i69m7jktjne85vydxr.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%2Fz8i69m7jktjne85vydxr.png" alt=" " width="799" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;ECS Express Mode will handle the deployment of the new version.&lt;/p&gt;

&lt;p&gt;This gives us a simple progression:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v1 → v2 → v3

Foundation → Advanced → Production
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Fn4lkh0551tvtlo5zem14.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%2Fn4lkh0551tvtlo5zem14.png" alt=" " width="800" height="548"&gt;&lt;/a&gt;&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%2Fpwk7b32k6dmhoelk5zhe.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%2Fpwk7b32k6dmhoelk5zhe.png" alt=" " width="800" height="568"&gt;&lt;/a&gt;&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%2Fukxr4mjprolsgiudsjbs.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%2Fukxr4mjprolsgiudsjbs.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And because the application itself changes between these versions, we can actually see the deployment update in the browser.&lt;/p&gt;




&lt;h1&gt;
  
  
  Traditional ECS vs ECS Express Mode
&lt;/h1&gt;

&lt;p&gt;Now that we have deployed the same application using both approaches, let's compare them.&lt;/p&gt;

&lt;p&gt;With traditional ECS, we had to explicitly configure the different pieces required to run our application.&lt;/p&gt;

&lt;p&gt;With Express Mode, we only needed to create an &lt;code&gt;aws_ecs_express_gateway_service&lt;/code&gt;, provide the required configuration, and let Express Mode take responsibility for much of the underlying infrastructure.&lt;/p&gt;

&lt;p&gt;Express Mode provisions and manages supporting components such as the &lt;strong&gt;Application Load Balancer, target group, security groups, HTTPS configuration, scaling policies, metrics, alarms, and health checks&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;On top of that, we get a much higher level of abstraction over the infrastructure, which makes the architecture significantly simpler.&lt;/p&gt;

&lt;p&gt;And this is where the main difference becomes clear:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Traditional ECS gives you more control, while ECS Express Mode gives you more abstraction.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  My Personal Verdict
&lt;/h1&gt;

&lt;p&gt;Even though I think &lt;strong&gt;ECS Express Mode is better than traditional ECS when it comes to simplicity and ease of deployment&lt;/strong&gt;, especially for beginners, I would still recommend learning and using &lt;strong&gt;traditional ECS first&lt;/strong&gt; if your goal is to properly understand ECS.&lt;/p&gt;

&lt;p&gt;With traditional ECS, you are forced to understand the underlying components and how they work together.&lt;/p&gt;

&lt;p&gt;You learn about task definitions, services, networking, security groups, load balancers, target groups, scaling, and all the other pieces that make an ECS deployment work.&lt;/p&gt;

&lt;p&gt;Once you understand those fundamentals, Express Mode becomes much easier to appreciate because you know &lt;strong&gt;what it is actually abstracting away&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So, for me:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning ECS → Traditional ECS 🧠&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Quick and simplified deployments → ECS Express Mode 🚀&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Cleanup
&lt;/h1&gt;

&lt;p&gt;Once you are done experimenting with the project, make sure to delete the resources to avoid unwanted AWS costs.&lt;/p&gt;

&lt;p&gt;First, destroy the Express Mode deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;ecs-express/express

terraform destroy &lt;span class="nt"&gt;--auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then destroy the traditional ECS deployment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../traditional/

terraform destroy &lt;span class="nt"&gt;--auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2F9qcncm2pdx0216a6j3sg.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%2F9qcncm2pdx0216a6j3sg.png" alt=" " width="800" height="598"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure both deployments are destroyed successfully before finishing.&lt;/p&gt;




&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;ECS Express Mode is an interesting addition to the ECS ecosystem because it changes how much infrastructure you need to think about when deploying a containerized application.&lt;/p&gt;

&lt;p&gt;With traditional ECS, you have much more control, but that also means more configuration and more AWS components to understand.&lt;/p&gt;

&lt;p&gt;With Express Mode, AWS takes care of a large portion of that infrastructure for you, allowing you to focus more on your application rather than wiring everything together.&lt;/p&gt;

&lt;p&gt;After building the same Flask application with both approaches, my biggest takeaway is that &lt;strong&gt;Express Mode isn't really replacing traditional ECS — it is providing another level of abstraction on top of it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are new to ECS, Express Mode can be a great way to get started quickly. But if you want to understand what is happening behind the scenes and build more customized architectures, traditional ECS is still the better place to learn.&lt;/p&gt;

&lt;p&gt;And honestly, that was the main reason I built this project — &lt;strong&gt;not just to deploy an application, but to understand what changes when AWS starts managing more of the infrastructure for us.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you found this comparison useful, feel free to connect with me and follow along as I continue exploring Cloud, DevOps, AWS, and Automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect with me
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Pravesh-Sudha" rel="noopener noreferrer"&gt;Pravesh Sudha&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/pravesh-sudha/" rel="noopener noreferrer"&gt;Pravesh Sudha&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;X/Twitter:&lt;/strong&gt; &lt;a href="https://x.com/PraveshSudha" rel="noopener noreferrer"&gt;@PraveshSudha&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;YouTube:&lt;/strong&gt; &lt;a href="https://www.youtube.com/@pravesh-sudha" rel="noopener noreferrer"&gt;Pravesh Sudha&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Thanks for reading!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adios Amigos, and see you in the next one! 👋&lt;/strong&gt;&lt;/p&gt;




</description>
      <category>aws</category>
      <category>devops</category>
      <category>cloud</category>
      <category>docker</category>
    </item>
    <item>
      <title>🚀 API Server: Explained like Never Before (Episode 4)</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Thu, 06 Aug 2026 04:30:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/api-server-explained-like-never-before-episode-4-2pjo</link>
      <guid>https://dev.to/aws-builders/api-server-explained-like-never-before-episode-4-2pjo</guid>
      <description>&lt;p&gt;Hola Amigos 👋&lt;/p&gt;

&lt;p&gt;Welcome to Episode 4 of &lt;strong&gt;K8s-With-Pravesh&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In the previous episode, we followed the complete journey of the &lt;code&gt;kubectl apply&lt;/code&gt; command and saw what really happens inside Kubernetes when we deploy an application. If you haven't checked it out yet, here it is:&lt;/p&gt;


&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws-builders/what-really-happens-when-you-run-kubectl-apply-episode-3-22kp" class="crayons-story__hidden-navigation-link"&gt;🤔 What Really Happens When You Run kubectl apply? Episode 3&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/V55bUAa9U04" title="🤔 What Really Happens When You Run kubectl apply? Episode 3"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/aws-builders"&gt;
            &lt;img alt="AWS Community Builders  logo" 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%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png" class="crayons-logo__image" width="350" height="350"&gt;
          &lt;/a&gt;

          &lt;a href="/pravesh_sudha_3c2b0c2b5e0" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2Fuser%2Fprofile_image%2F1805492%2F62f81727-4167-4aa7-9890-40fa2d850525.png" alt="pravesh_sudha_3c2b0c2b5e0 profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pravesh_sudha_3c2b0c2b5e0" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Pravesh Sudha
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Pravesh Sudha
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
                
              
              &lt;div id="story-author-preview-content-4199128" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/pravesh_sudha_3c2b0c2b5e0" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1805492%2F62f81727-4167-4aa7-9890-40fa2d850525.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Pravesh Sudha&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/aws-builders" class="crayons-story__secondary fw-medium"&gt;AWS Community Builders &lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws-builders/what-really-happens-when-you-run-kubectl-apply-episode-3-22kp" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 21&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aws-builders/what-really-happens-when-you-run-kubectl-apply-episode-3-22kp" id="article-link-4199128"&gt;
          🤔 What Really Happens When You Run kubectl apply? Episode 3
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devplusplus"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devplusplus&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devops"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devops&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/kubernetes"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;kubernetes&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/cloud"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;cloud&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aws-builders/what-really-happens-when-you-run-kubectl-apply-episode-3-22kp" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;9&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aws-builders/what-really-happens-when-you-run-kubectl-apply-episode-3-22kp#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


&lt;p&gt;In that blog, our journey started with the &lt;strong&gt;front-door keeper&lt;/strong&gt; of the Kubernetes cluster: the &lt;strong&gt;API Server&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The API Server is the central administrative hub and the &lt;strong&gt;only entry point&lt;/strong&gt; for managing a Kubernetes cluster. It exposes a RESTful HTTP API that processes, validates, and stores requests for Kubernetes objects like Pods, Services, Deployments, ConfigMaps, and much more.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Interesting Fact:&lt;/strong&gt; The Kubernetes API Server is completely &lt;strong&gt;stateless&lt;/strong&gt;. It doesn't permanently store anything by itself. Instead, it relies on &lt;strong&gt;etcd&lt;/strong&gt; (Kubernetes' distributed key-value database) to store the entire cluster state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  How does the API Server work?
&lt;/h2&gt;

&lt;p&gt;Whenever we execute a command like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the request travels through the API Server in a highly structured lifecycle:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Request ]
      ↓
[ Authentication ]
      ↓
[ Authorization (RBAC) ]
      ↓
[ Mutating Admission ]
      ↓
[ Schema Validation ]
      ↓
[ Validating Admission ]
      ↓
[ etcd Data Store ]
      ↓
[ Watch Events ]
      ↓
[ Controller Manager ]
      ↓
[ Scheduler ]
      ↓
[ Kubelet ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything inside Kubernetes starts with the API Server.&lt;/p&gt;

&lt;p&gt;Once a request reaches the API Server, it is first &lt;strong&gt;authenticated&lt;/strong&gt; using client certificates, tokens, or other authentication methods. After that, &lt;strong&gt;authorization&lt;/strong&gt; checks whether the user has permission to perform the requested action using RBAC (Role-Based Access Control).&lt;/p&gt;

&lt;p&gt;Next come the &lt;strong&gt;Admission Controllers&lt;/strong&gt;. These can modify the request, inject default values, or reject it before it reaches the cluster.&lt;/p&gt;

&lt;p&gt;The object is then checked against the official Kubernetes schema to ensure it follows the correct structure. If everything is valid, the API Server stores the object inside &lt;strong&gt;etcd&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;But the story doesn't end there.&lt;/p&gt;

&lt;p&gt;Once the object is stored, every Kubernetes component watching the API Server immediately notices the change. The Controller Manager starts reconciliation, the Scheduler decides where the Pod should run, and finally the Kubelet on the selected node creates the container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Beginner Mistake:&lt;/strong&gt; Many beginners think &lt;code&gt;kubectl&lt;/code&gt; directly talks to a Deployment and creates Pods. In reality, &lt;code&gt;kubectl&lt;/code&gt; only sends a request to the API Server. Everything else happens because multiple Kubernetes components continuously reconcile the desired state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If we simplify what happens after running &lt;code&gt;kubectl apply&lt;/code&gt;, the flow looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; reads the YAML.&lt;/li&gt;
&lt;li&gt;Converts it into JSON.&lt;/li&gt;
&lt;li&gt;Creates an HTTPS REST request.&lt;/li&gt;
&lt;li&gt;Sends it to the API Server.&lt;/li&gt;
&lt;li&gt;API Server authenticates the request.&lt;/li&gt;
&lt;li&gt;API Server authorizes the request.&lt;/li&gt;
&lt;li&gt;Admission Controllers validate and mutate the object.&lt;/li&gt;
&lt;li&gt;The object is stored inside etcd.&lt;/li&gt;
&lt;li&gt;Controllers notice the change.&lt;/li&gt;
&lt;li&gt;The Scheduler assigns a node.&lt;/li&gt;
&lt;li&gt;The Kubelet creates the container.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Key functions of the API Server
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Single Source of Truth&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The API Server is the &lt;strong&gt;only&lt;/strong&gt; Kubernetes component that communicates directly with &lt;strong&gt;etcd&lt;/strong&gt;. No Scheduler, Controller Manager, or Kubelet ever talks to etcd directly.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The Watch Mechanism&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Instead of continuously polling etcd, Kubernetes components use the API Server's &lt;strong&gt;Watch API&lt;/strong&gt;. This allows them to receive real-time updates whenever the cluster state changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Cluster Synchronization&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The API Server acts as the coordinator between the control plane and worker nodes. Every component observes changes through the API Server and works toward making the actual state match the desired state.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's see it in action
&lt;/h2&gt;

&lt;p&gt;To see the API Server in real life, start a Minikube cluster and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl proxy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You will see a list of Kubernetes API endpoints.&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%2Fhqfh1drzx169suuwexs0.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%2Fhqfh1drzx169suuwexs0.png" alt=" " width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is your Kubernetes API Server exposing RESTful APIs.&lt;/p&gt;

&lt;p&gt;If you visit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8001/api
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;you'll see the core Kubernetes API information.&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%2Fj165qpepqhf4087a5xa6.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%2Fj165qpepqhf4087a5xa6.png" alt=" " width="800" height="606"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's do something even more interesting.&lt;/p&gt;

&lt;p&gt;Remember the Deployment we created in Episode 3? Apply that Deployment again using &lt;code&gt;kubectl apply&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once the Pods are running, open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:8001/api/v1/namespaces/default/pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see a large JSON document describing every Pod in the default namespace.&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%2Fi5ozwhv1p5agna9ixvew.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%2Fi5ozwhv1p5agna9ixvew.png" alt=" " width="800" height="344"&gt;&lt;/a&gt;&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%2Fzrrp7sxyiq9nwn3kgpne.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%2Fzrrp7sxyiq9nwn3kgpne.png" alt=" " width="800" height="478"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the same information that &lt;code&gt;kubectl get pods&lt;/code&gt; displays in a much cleaner format. &lt;code&gt;kubectl&lt;/code&gt; is simply calling the Kubernetes API Server and presenting the response in a human-readable way.&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%2F2lvtau9p5h0v8wxu7hz1.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%2F2lvtau9p5h0v8wxu7hz1.png" alt=" " width="800" height="114"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can even compare the Pod names and IDs with the output of &lt;code&gt;kubectl get pods&lt;/code&gt; and see that they're exactly the same.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why do different Kubernetes objects have different API versions?
&lt;/h2&gt;

&lt;p&gt;When I first started learning Kubernetes, I always wondered why different resources had different API versions.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pods use &lt;code&gt;v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Deployments use &lt;code&gt;apps/v1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Jobs use &lt;code&gt;batch/v1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The reason is simple.&lt;/p&gt;

&lt;p&gt;Pods belong to the &lt;strong&gt;Core API Group&lt;/strong&gt;, so they use &lt;code&gt;v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Deployments belong to the &lt;strong&gt;Apps API Group&lt;/strong&gt;, so they use &lt;code&gt;apps/v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Similarly, Jobs belong to the &lt;strong&gt;Batch API Group&lt;/strong&gt;, so they use &lt;code&gt;batch/v1&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;As Kubernetes evolved, related resources were grouped into different API groups, each with its own versioning.&lt;/p&gt;

&lt;p&gt;Every communication happening inside a Kubernetes cluster goes through the API Server. Even though etcd stores the cluster data, no component reads or writes directly to it.&lt;/p&gt;

&lt;p&gt;For example, when you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get deployments
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt; sends a request to the API Server.&lt;/p&gt;

&lt;p&gt;The API Server reads the Deployment object from etcd and returns the response back to &lt;code&gt;kubectl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Everything flows through the API Server.&lt;/p&gt;

&lt;p&gt;That's the end of our deep dive into the Kubernetes API Server.&lt;/p&gt;

&lt;p&gt;I hope this helped you understand how the API Server isn't just the front door of Kubernetes, but also the central communication hub that connects every component inside the cluster.&lt;/p&gt;

&lt;p&gt;In the next episode, we'll explore the &lt;strong&gt;data hub of Kubernetes&lt;/strong&gt;—&lt;strong&gt;etcd&lt;/strong&gt;—and understand how Kubernetes stores and manages the entire cluster state.&lt;/p&gt;

&lt;p&gt;If you enjoyed this blog, make sure to follow me on LinkedIn, X, YouTube, GitHub, Dev.to, and Medium.&lt;/p&gt;

&lt;p&gt;Till then,&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Happy Coding 👋&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cloud</category>
      <category>devops</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🤔 What Really Happens When You Run kubectl apply? Episode 3</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Tue, 21 Jul 2026 17:38:29 +0000</pubDate>
      <link>https://dev.to/aws-builders/what-really-happens-when-you-run-kubectl-apply-episode-3-22kp</link>
      <guid>https://dev.to/aws-builders/what-really-happens-when-you-run-kubectl-apply-episode-3-22kp</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Follow the complete journey of a Kubernetes Deployment—from your terminal to a running container inside the cluster.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most Kubernetes tutorials teach us how to create resources:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few seconds later, Pods start running.&lt;/p&gt;

&lt;p&gt;Simple, right?&lt;/p&gt;

&lt;p&gt;But Kubernetes is doing far more work than most people realize.&lt;/p&gt;

&lt;p&gt;Hola Amigos! Welcome to the third episode of &lt;strong&gt;K8s with Pravesh&lt;/strong&gt;. Today, we're going to dive into the lifecycle of one of the most commonly used Kubernetes commands: &lt;strong&gt;&lt;code&gt;kubectl apply&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Behind this simple command lies a chain of events involving the &lt;strong&gt;API Server, ETCD, Controller Manager, Scheduler, Kubelet,&lt;/strong&gt; and &lt;strong&gt;Container Runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Understanding this workflow is one of the biggest steps toward becoming a better Kubernetes engineer. In this article, we'll trace every step that occurs after running &lt;code&gt;kubectl apply&lt;/code&gt; and see how Kubernetes transforms a YAML file into a running application.&lt;/p&gt;

&lt;p&gt;So, without further ado, let's get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  How Does the Information Flow?
&lt;/h2&gt;

&lt;p&gt;Before we dive deep, let's first get ourselves familiar with &lt;strong&gt;kubectl&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl&lt;/code&gt; is the command-line tool that helps us interact with a Kubernetes cluster. Whenever a user runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;the YAML file is sent to the &lt;strong&gt;API Server&lt;/strong&gt;, which acts as the front door of Kubernetes.&lt;/p&gt;

&lt;p&gt;The API Server authenticates the request, validates the manifest, and processes Kubernetes objects such as Deployments, Services, Pods, ReplicaSets, and many others.&lt;/p&gt;

&lt;p&gt;Once the API Server has the YAML, you might wonder:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Does Kubernetes create the Pods immediately?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is &lt;strong&gt;No.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Instead of creating Pods right away, Kubernetes first stores the &lt;strong&gt;desired state&lt;/strong&gt; of the resources inside &lt;strong&gt;ETCD&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;ETCD acts as the primary database—and often the "brain"—of the Kubernetes cluster. Just like in GitOps, where Git serves as the single source of truth, ETCD is the single source of truth for the cluster. It stores the complete cluster state, configuration data, and metadata.&lt;/p&gt;

&lt;p&gt;Now you might be wondering:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Okay, the data is stored in ETCD... but who reads it?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is where the &lt;strong&gt;Controller Manager&lt;/strong&gt; comes into the picture.&lt;/p&gt;

&lt;p&gt;The Controller Manager is one of the core control plane components. It continuously watches the desired state stored in ETCD and compares it with the current state of the cluster. Whenever it detects a difference, it takes action to reconcile them.&lt;/p&gt;

&lt;p&gt;This reconciliation loop is also what enables Kubernetes' self-healing capabilities. For example, if a Pod crashes unexpectedly, the Controller Manager notices the mismatch and automatically creates a replacement Pod to restore the desired state.&lt;/p&gt;

&lt;p&gt;Inside our manifest, we only created a &lt;strong&gt;Deployment&lt;/strong&gt;. We never explicitly created a &lt;strong&gt;ReplicaSet&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Kubernetes takes care of that for us.&lt;/p&gt;

&lt;p&gt;The Deployment Controller automatically creates a ReplicaSet, and the ReplicaSet Controller ensures that the desired number of Pods are always running.&lt;/p&gt;

&lt;p&gt;Now comes another important question:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"We have the Pods ready, but who decides where they'll run?"&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This is the job of the &lt;strong&gt;Scheduler&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The Scheduler is responsible for assigning pending Pods to the most suitable node in the cluster.&lt;/p&gt;

&lt;p&gt;It does this in two stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filtering&lt;/strong&gt; – Removes nodes that don't satisfy the Pod's requirements, such as available resources, taints, tolerations, affinity, or node selectors.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoring&lt;/strong&gt; – Evaluates the remaining nodes and selects the best one based on Kubernetes' scheduling algorithms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the Scheduler selects a node, the &lt;strong&gt;Kubelet&lt;/strong&gt;—the primary node agent running on every worker node—takes over.&lt;/p&gt;

&lt;p&gt;The Kubelet watches the API Server for Pods assigned to its node. Once it receives the instruction, it communicates with the container runtime (such as &lt;strong&gt;containerd&lt;/strong&gt;) and ensures that the desired state is achieved.&lt;/p&gt;

&lt;p&gt;Finally, the &lt;strong&gt;container runtime&lt;/strong&gt; pulls the required container image (if it isn't already available), creates the container, and starts it.&lt;/p&gt;

&lt;p&gt;And that's how a simple &lt;code&gt;kubectl apply&lt;/code&gt; command works under the hood.&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%2Fy10ozwnrgxt7bc9jdde7.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%2Fy10ozwnrgxt7bc9jdde7.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Kubernetes Is Not Magic
&lt;/h2&gt;

&lt;p&gt;When beginners first start learning Kubernetes, it almost feels like magic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;...and boom! Your application is up and running in no time.&lt;/p&gt;

&lt;p&gt;But in reality, Kubernetes is a collection of specialized components working together through a continuous reconciliation process.&lt;/p&gt;

&lt;p&gt;Every component has a specific responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Server accepts and validates requests.&lt;/li&gt;
&lt;li&gt;ETCD stores the cluster state.&lt;/li&gt;
&lt;li&gt;Controller Manager reconciles the desired and current state.&lt;/li&gt;
&lt;li&gt;Scheduler selects the best node for Pods.&lt;/li&gt;
&lt;li&gt;Kubelet manages workloads on each node.&lt;/li&gt;
&lt;li&gt;Container Runtime pulls images and runs containers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you understand this flow, troubleshooting Kubernetes becomes much less stressful because you know exactly where to look when something breaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And that's the journey of a simple &lt;code&gt;kubectl apply&lt;/code&gt; command.&lt;/p&gt;

&lt;p&gt;What looks like a single command from our terminal is actually a well-orchestrated sequence of events involving multiple Kubernetes components, each doing one specific job. This separation of responsibilities is what makes Kubernetes powerful, scalable, and resilient.&lt;/p&gt;

&lt;p&gt;I hope this article helped you understand not just &lt;strong&gt;what&lt;/strong&gt; Kubernetes does, but &lt;strong&gt;how&lt;/strong&gt; it does it behind the scenes.&lt;/p&gt;

&lt;p&gt;This is just the beginning of our Kubernetes Internals journey. In the upcoming articles, we'll dive deeper into individual components like the API Server, ETCD, Scheduler, Kubelet, and many more to understand how they work under the hood.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article, consider following me on my socials, where I regularly share content around Kubernetes, AWS, DevOps, and Cloud Engineering.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🌐 Blog: &lt;a href="//blog.praveshsudha.com"&gt;blog.praveshsudha.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💼 LinkedIn: &lt;a href="https://linkedin.com/in/pravesh-sudha" rel="noopener noreferrer"&gt;Pravesh Sudha&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;🐦 X (Twitter): &lt;a href="https://x.com/praveshstwt" rel="noopener noreferrer"&gt;@praveshstwt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;💻 GitHub: &lt;a href="https://github.com/Pravesh-Sudha" rel="noopener noreferrer"&gt;Pravesh-Sudha&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;📺 YouTube: &lt;a href="https://www.youtube.com/@pravesh-sudha" rel="noopener noreferrer"&gt;Pravesh Sudha&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;See you in the next episode of &lt;strong&gt;K8s with Pravesh&lt;/strong&gt;. 🚀&lt;/p&gt;

</description>
      <category>devplusplus</category>
      <category>devops</category>
      <category>kubernetes</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Just uploaded a video on AI Prompting techniques ⬇️:

https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Wed, 08 Jul 2026 15:26:47 +0000</pubDate>
      <link>https://dev.to/pravesh_sudha_3c2b0c2b5e0/just-uploaded-a-video-on-ai-prompting-techniques--4g27</link>
      <guid>https://dev.to/pravesh_sudha_3c2b0c2b5e0/just-uploaded-a-video-on-ai-prompting-techniques--4g27</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm" class="crayons-story__hidden-navigation-link"&gt;Here's why your Prompt is WRONG 😑&lt;/a&gt;
    &lt;div class="crayons-article__cover crayons-article__cover__image__feed"&gt;
      &lt;iframe src="https://www.youtube.com/embed/4QTeb4x3918" title="Here's why your Prompt is WRONG 😑"&gt;&lt;/iframe&gt;
    &lt;/div&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/aws-builders"&gt;
            &lt;img alt="AWS Community Builders  logo" 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%2Forganization%2Fprofile_image%2F2794%2F88da75b6-aadd-4ea1-8083-ae2dfca8be94.png" class="crayons-logo__image" width="350" height="350"&gt;
          &lt;/a&gt;

          &lt;a href="/pravesh_sudha_3c2b0c2b5e0" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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%2Fuser%2Fprofile_image%2F1805492%2F62f81727-4167-4aa7-9890-40fa2d850525.png" alt="pravesh_sudha_3c2b0c2b5e0 profile" class="crayons-avatar__image" width="96" height="96"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/pravesh_sudha_3c2b0c2b5e0" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Pravesh Sudha
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Pravesh Sudha
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
              
              &lt;div id="story-author-preview-content-3779851" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/pravesh_sudha_3c2b0c2b5e0" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1805492%2F62f81727-4167-4aa7-9890-40fa2d850525.png" class="crayons-avatar__image" alt="" width="96" height="96"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Pravesh Sudha&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/aws-builders" class="crayons-story__secondary fw-medium"&gt;AWS Community Builders &lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 29&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm" id="article-link-3779851"&gt;
          Here's why your Prompt is WRONG 😑
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ai"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ai&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/promptengineering"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;promptengineering&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/programming"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;programming&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/beginners"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;beginners&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;9&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            3 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
    </item>
    <item>
      <title>🤔 If Data Survives in Deployments, Why Do We Need StatefulSets? | Episode 2</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Mon, 01 Jun 2026 17:56:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/if-data-survives-in-deployments-why-do-we-need-statefulsets-31km</link>
      <guid>https://dev.to/aws-builders/if-data-survives-in-deployments-why-do-we-need-statefulsets-31km</guid>
      <description>&lt;p&gt;Hola Amigos 👋&lt;/p&gt;

&lt;p&gt;Welcome to the second episode of &lt;strong&gt;K8s with Pravesh&lt;/strong&gt;. If you're new to the series, check out the &lt;a href="https://dev.to/aws-builders/kubernetes-for-beginners-deploying-an-nginx-node-redis-application-1o7h"&gt;first episode&lt;/a&gt; where we learned how Deployments and Services work under the hood.&lt;/p&gt;

&lt;p&gt;Most people believe that StatefulSets are needed because Deployments lose data. But is that actually true?&lt;/p&gt;

&lt;p&gt;In today's blog, we'll answer this question by exploring &lt;strong&gt;StatefulSets vs Deployments&lt;/strong&gt; through a practical hands-on demonstration.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Getting Started
&lt;/h2&gt;

&lt;p&gt;Head over to my &lt;a href="https://github.com/Pravesh-Sudha/K8s_with_Pravesh" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; repository, fork it under your own GitHub account, and clone the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/&amp;lt;your-username&amp;gt;/K8s_with_Pravesh.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Navigate to the project directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;K8s_with_Pravesh/part-02-statefulsets/configs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here you'll find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Secrets and ConfigMaps&lt;/li&gt;
&lt;li&gt;Deployment manifest&lt;/li&gt;
&lt;li&gt;StatefulSet manifest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First, apply the Secrets and ConfigMaps. These contain the MySQL password, database name, and MySQL initialization script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; secrets-and-config.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🧪 Experiment 1: Deployment + PVC
&lt;/h2&gt;

&lt;p&gt;Now let's apply our Deployment manifest.&lt;/p&gt;

&lt;p&gt;This file contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persistent Volume Claim (PVC)&lt;/li&gt;
&lt;li&gt;Deployment&lt;/li&gt;
&lt;li&gt;Service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Apply it using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the pod is running, we'll create a sample record inside MySQL, delete the pod, and verify whether the data survives.&lt;/p&gt;

&lt;p&gt;Get the pod name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mysql-XXXXXX-XXXX &lt;span class="nt"&gt;--&lt;/span&gt; mysql &lt;span class="nt"&gt;-uroot&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enter the password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pravesh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(You can change this value in the &lt;code&gt;secrets-and-config.yml&lt;/code&gt; file.)&lt;/p&gt;

&lt;p&gt;Inside MySQL, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;crud_app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Pravesh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'pravesh@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'secret'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fdkx7o38um3o7rz3pkau0.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.amazonaws.com%2Fuploads%2Farticles%2Fdkx7o38um3o7rz3pkau0.png" alt=" " width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Delete the pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pod mysql-XXXXXX-XXXX
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for Kubernetes to create a replacement pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to the new pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mysql-XXXXXX-XXXX &lt;span class="nt"&gt;--&lt;/span&gt; mysql &lt;span class="nt"&gt;-uroot&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;crud_app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2F33f1trg1hhd6yv83jgzk.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.amazonaws.com%2Fuploads%2Farticles%2F33f1trg1hhd6yv83jgzk.png" alt=" " width="800" height="523"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You'll notice that the data is still there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait... The Data Survived?
&lt;/h3&gt;

&lt;p&gt;At this point, many people expect the data to disappear because we're using a Deployment.&lt;/p&gt;

&lt;p&gt;However, the data survived.&lt;/p&gt;

&lt;p&gt;So the obvious question becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If data survives in a Deployment, why do we even need StatefulSets?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer is simple.&lt;/p&gt;

&lt;p&gt;The data survived because we attached a Persistent Volume Claim (PVC) to the MySQL container. The PVC preserved the data, not the Deployment.&lt;/p&gt;

&lt;p&gt;In other words:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pod
 ↓
PVC
 ↓
Persistent Storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pod can disappear and be recreated, but the storage remains intact.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Experiment 2: StatefulSet
&lt;/h2&gt;

&lt;p&gt;Now let's perform the same experiment using a StatefulSet.&lt;/p&gt;

&lt;p&gt;Apply the StatefulSet manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; statefulset.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the pod to start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll notice that the pod has a predictable name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql-0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connect to MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mysql-0 &lt;span class="nt"&gt;--&lt;/span&gt; mysql &lt;span class="nt"&gt;-uroot&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Insert another record:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;crud_app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;password&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Pravesh'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'pravesh@example.com'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'secret'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Exit MySQL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;exit&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete the pod:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pod mysql-0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for Kubernetes to recreate it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql-0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pod name remains exactly the same.&lt;/p&gt;

&lt;p&gt;Connect again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; mysql-0 &lt;span class="nt"&gt;--&lt;/span&gt; mysql &lt;span class="nt"&gt;-uroot&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;USE&lt;/span&gt; &lt;span class="n"&gt;crud_app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data survived once again.&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.amazonaws.com%2Fuploads%2Farticles%2Fuhrvjl76cydxxtspynmh.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.amazonaws.com%2Fuploads%2Farticles%2Fuhrvjl76cydxxtspynmh.png" alt=" " width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🤨 So What Is the Real Difference?
&lt;/h2&gt;

&lt;p&gt;Let's inspect the PVCs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pvc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fahije381wzyr9kunw7lz.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.amazonaws.com%2Fuploads%2Farticles%2Fahije381wzyr9kunw7lz.png" alt=" " width="799" height="172"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With StatefulSets, each replica gets its own dedicated PVC.&lt;/p&gt;

&lt;p&gt;For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mysql-data-mysql-0
mysql-data-mysql-1
mysql-data-mysql-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates a stable relationship between a pod and its storage.&lt;/p&gt;

&lt;p&gt;StatefulSets manage a group of pods while maintaining a sticky identity for each pod. Unlike Deployments, StatefulSet pods are not interchangeable.&lt;/p&gt;

&lt;p&gt;Each pod receives:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A stable hostname&lt;/li&gt;
&lt;li&gt;A stable network identity&lt;/li&gt;
&lt;li&gt;Persistent storage&lt;/li&gt;
&lt;li&gt;Ordered deployment and termination&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if a pod is rescheduled, it retains its identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Should You Use StatefulSets?
&lt;/h2&gt;

&lt;p&gt;StatefulSets are valuable when your application requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stable, unique network identifiers&lt;/li&gt;
&lt;li&gt;Stable, persistent storage&lt;/li&gt;
&lt;li&gt;Ordered, graceful deployment and scaling&lt;/li&gt;
&lt;li&gt;Ordered rolling updates&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Common examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Kafka&lt;/li&gt;
&lt;li&gt;ZooKeeper&lt;/li&gt;
&lt;li&gt;Redis Clusters&lt;/li&gt;
&lt;li&gt;Elasticsearch&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment vs StatefulSet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Deployment&lt;/th&gt;
&lt;th&gt;StatefulSet&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Data Persistence&lt;/td&gt;
&lt;td&gt;✅ With PVC&lt;/td&gt;
&lt;td&gt;✅ With PVC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable Pod Name&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stable Network Identity&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dedicated Storage Per Replica&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ordered Startup/Shutdown&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A common misconception is that StatefulSets exist because Deployments cannot persist data. As we saw in this blog, that isn't entirely true.&lt;/p&gt;

&lt;p&gt;A Deployment can preserve data just fine when paired with a Persistent Volume Claim. The PVC is responsible for data persistence, not the Deployment itself.&lt;/p&gt;

&lt;p&gt;The real strength of StatefulSets lies in providing stable identities, predictable networking, dedicated storage per replica, and ordered deployment behavior. These features make StatefulSets the ideal choice for databases and other stateful distributed systems.&lt;/p&gt;

&lt;p&gt;If you're running stateless applications such as frontend applications, REST APIs, or microservices, Deployments are usually the right choice. But when your workloads require stable identities and persistent state, StatefulSets become essential.&lt;/p&gt;

&lt;p&gt;I hope this hands-on comparison helped clarify the difference between Deployments and StatefulSets.&lt;/p&gt;

&lt;p&gt;See you in the next episode of &lt;strong&gt;K8s with Pravesh&lt;/strong&gt;! 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  🌟 Connect With Me
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://praveshsudha.com" rel="noopener noreferrer"&gt;https://praveshsudha.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Blog: &lt;a href="https://blog.praveshsudha.com" rel="noopener noreferrer"&gt;https://blog.praveshsudha.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/pravesh-sudha/" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/pravesh-sudha/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;X (Twitter): &lt;a href="https://x.com/praveshstwt" rel="noopener noreferrer"&gt;https://x.com/praveshstwt&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;YouTube: &lt;a href="https://www.youtube.com/@pravesh-sudha" rel="noopener noreferrer"&gt;https://www.youtube.com/@pravesh-sudha&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you found this article helpful, consider sharing it with your network and following the series for upcoming Kubernetes content.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>automation</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Here's why your Prompt is WRONG 😑</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Fri, 29 May 2026 17:48:43 +0000</pubDate>
      <link>https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm</link>
      <guid>https://dev.to/aws-builders/heres-why-your-prompt-is-wrong-3dlm</guid>
      <description>&lt;p&gt;The right prompt is no longer just a skill — it is becoming a necessity in this fast-paced world where almost everything is driven by AI chatbots and agents.&lt;/p&gt;

&lt;p&gt;A lot of people think AI gives bad results because the model is not powerful enough, but in most cases, the real issue is the prompt itself.&lt;/p&gt;

&lt;p&gt;In today’s blog, we will uncover some of the most useful AI prompting techniques that can help you write better prompts and get significantly better results.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Zero-Shot Prompting
&lt;/h2&gt;

&lt;p&gt;This is the most common prompting technique among beginners. Almost everyone starts from this approach.&lt;/p&gt;

&lt;p&gt;In Zero-Shot Prompting, you directly ask the AI what you need in a brief and specific way without giving any prior examples.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;p&gt;Instead of writing:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Generate me a Kubernetes Deployment file”&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.amazonaws.com%2Fuploads%2Farticles%2F2mz44fm6hk9hh5fp68bq.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.amazonaws.com%2Fuploads%2Farticles%2F2mz44fm6hk9hh5fp68bq.png" alt=" " width="799" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can write:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Generate ONLY a Kubernetes Deployment file”&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.amazonaws.com%2Fuploads%2Farticles%2Fjbr3c84fbyy7sl0xqn9z.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.amazonaws.com%2Fuploads%2Farticles%2Fjbr3c84fbyy7sl0xqn9z.png" alt=" " width="799" height="573"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This small change cuts out unnecessary explanations, extra commands, and long guides that AI models often generate by default.&lt;/p&gt;

&lt;p&gt;Zero-Shot Prompting works best for popular or familiar use cases where the AI already has strong contextual understanding.&lt;/p&gt;

&lt;p&gt;Another advantage of this approach is lower token usage. In the screenshots above, you can notice that the token usage is almost 30% lower compared to longer prompts. This becomes extremely important in large organizations where APIs frequently interact with AI systems at scale.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Few-Shot Prompting
&lt;/h2&gt;

&lt;p&gt;In this approach, before giving the actual task, we first provide the AI with a few examples.&lt;/p&gt;

&lt;p&gt;This helps the model become context-aware and understand the expected style, structure, or format of the output.&lt;/p&gt;

&lt;p&gt;Few-Shot Prompting is especially useful when organizations want responses to follow a particular standard rather than simply generating the “ideal” answer.&lt;/p&gt;

&lt;p&gt;For example, if a company wants all incident reports, YAML files, or summaries to follow a fixed structure, giving examples beforehand helps maintain consistency.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For Fun: I have attached a SuperHero Example&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.amazonaws.com%2Fuploads%2Farticles%2Fy7onv8rewotzy9eldae4.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.amazonaws.com%2Fuploads%2Farticles%2Fy7onv8rewotzy9eldae4.png" alt=" " width="799" height="318"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Multi-Shot Prompting
&lt;/h2&gt;

&lt;p&gt;Multi-Shot Prompting is very similar to Few-Shot Prompting, but instead of providing a few examples, we provide many examples for even better contextual understanding.&lt;/p&gt;

&lt;p&gt;The advantage is usually better and more refined output quality.&lt;/p&gt;

&lt;p&gt;However, the downside is increased token consumption because the input becomes significantly larger due to additional examples.&lt;/p&gt;

&lt;p&gt;This is a tradeoff between output quality and cost efficiency.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Chain of Thought Prompting
&lt;/h2&gt;

&lt;p&gt;Chain of Thought Prompting encourages the AI to break down complex reasoning tasks into intermediate steps before generating the final answer.&lt;/p&gt;

&lt;p&gt;For example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Explain why this deployment failed in a step-by-step manner.”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This technique is extremely useful for debugging, analysis, problem-solving, and learning deeply about a topic instead of just scratching the surface.&lt;/p&gt;

&lt;p&gt;It is especially beneficial for curious minds who want to understand &lt;em&gt;why&lt;/em&gt; something happened rather than simply receiving the final answer.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. RAG (Retrieval-Augmented Generation)
&lt;/h2&gt;

&lt;p&gt;RAG is not exactly a prompting technique, but more of a workflow approach used alongside prompting.&lt;/p&gt;

&lt;p&gt;In this method, the AI is connected to external data sources such as databases, PDFs, internal documents, or APIs.&lt;/p&gt;

&lt;p&gt;Instead of relying solely on its internal training data, the model retrieves relevant information from these external sources before generating a response.&lt;/p&gt;

&lt;p&gt;This helps produce more accurate, contextual, and up-to-date answers.&lt;/p&gt;

&lt;p&gt;RAG is widely used in AI agents, enterprise chatbots, documentation assistants, and knowledge-based systems.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Writing an efficient prompt is not as difficult as it seems.&lt;/p&gt;

&lt;p&gt;It simply involves understanding which technique works best for your specific use case.&lt;/p&gt;

&lt;p&gt;Experiment with different prompting approaches, observe the outputs, and gradually build your own prompting style.&lt;/p&gt;

&lt;p&gt;If you liked this blog, make sure to follow me on &lt;a href="https://www.linkedin.com/in/pravesh-sudha/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;, &lt;a href="https://x.com/praveshstwt" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;, and &lt;a href="https://www.youtube.com/@pravesh-sudha" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt; where I regularly share my learnings around AI, DevOps, and technology.&lt;/p&gt;

&lt;p&gt;Till then,&lt;/p&gt;

&lt;p&gt;Adios 👋&lt;/p&gt;

</description>
      <category>ai</category>
      <category>promptengineering</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🤫 Firebase Is Quietly Preparing for an Offline-First AI Future</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Fri, 22 May 2026 17:10:50 +0000</pubDate>
      <link>https://dev.to/pravesh_sudha_3c2b0c2b5e0/firebase-is-quietly-preparing-for-an-offline-first-ai-future-5die</link>
      <guid>https://dev.to/pravesh_sudha_3c2b0c2b5e0/firebase-is-quietly-preparing-for-an-offline-first-ai-future-5die</guid>
      <description>&lt;p&gt;&lt;a href="https://www.youtube.com/live/lMEfqmyRMA8" rel="noopener noreferrer"&gt;Firebase&lt;/a&gt; announcements at Google I/O 2026 covered an array of products and features, but the one that grabbed my attention the most was Firebase itself. Most people are understandably focused on Gemini integrations, AI Studio, and the new SQL capabilities inside Firebase, but I believe there is something deeper happening underneath these announcements.&lt;/p&gt;

&lt;p&gt;Firebase introduced offline caching support, which helps applications remain responsive even in little or no connectivity. Combined with local and hybrid AI inference, this suggests that Firebase is quietly moving toward an offline-first, hybrid-intelligence model.&lt;/p&gt;

&lt;p&gt;A large number of companies are transitioning to the cloud because of the convenience of not managing physical infrastructure and data servers. However, cloud dependency comes with its own trade-offs. Heavy reliance on cloud infrastructure introduces dependence on continuous high-speed internet connectivity, recurring subscription costs, and potential vendor lock-in.&lt;/p&gt;

&lt;p&gt;The reality is that not every place in the world has fast and stable internet connectivity. Offline caching can help reduce cloud costs while improving application responsiveness, even in low-connectivity environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  Modern Applications Are Too Cloud Dependent
&lt;/h2&gt;

&lt;p&gt;Modern applications often assume that users:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;have constant internet access,&lt;/li&gt;
&lt;li&gt;can perform fast cloud API calls with low latency,&lt;/li&gt;
&lt;li&gt;and are always connected to online AI services.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In reality, connectivity is far from universal, especially in rural areas, trains, crowded public networks, and emerging markets such as India.&lt;/p&gt;

&lt;p&gt;AI has further increased cloud dependence because AI applications continuously send prompts, images, voice data, and user content to remote servers. This increases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;latency,&lt;/li&gt;
&lt;li&gt;cloud costs,&lt;/li&gt;
&lt;li&gt;bandwidth usage,&lt;/li&gt;
&lt;li&gt;and potential privacy concerns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, “smart applications” can quickly become fragile applications when internet connectivity is lost. AI features stop functioning, synchronization fails, and the overall user experience degrades significantly.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Firebase Actually Announced
&lt;/h2&gt;

&lt;p&gt;Firebase introduced custom resolvers, allowing developers to extend Firebase Data Connect beyond Cloud SQL and integrate additional data sources. Alongside this, realtime sync improves application UX by enabling live updates and synchronization across devices.&lt;/p&gt;

&lt;p&gt;However, the most interesting feature, in my opinion, is offline cache support, which helps applications remain responsive even with limited or no connectivity. Firebase AI Logic also supports local inference with cloud fallback, allowing certain AI workloads to run directly on-device while heavier tasks can still rely on cloud infrastructure when required.&lt;/p&gt;

&lt;p&gt;Additionally, Firebase AI Logic simplifies the integration of generative AI features without requiring extensive server-side setup. It supports multiple programming languages, including Kotlin, Java, Swift, and Flutter.&lt;/p&gt;

&lt;p&gt;Taken together, these are not isolated features. Firebase appears to be gradually reducing dependence on centralized cloud execution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Firebase Is Moving Toward an Offline-First AI Architecture
&lt;/h2&gt;

&lt;p&gt;With offline caching, applications can remain usable even without network connectivity by treating local application state as a first-class component. Synchronization can happen later once connectivity is restored. This improves responsiveness, resilience, and overall application UX while reducing the frequency of frustrating “No Internet Connection” screens.&lt;/p&gt;

&lt;p&gt;Local AI inference also changes the compute model. Instead of every AI request depending entirely on cloud APIs, certain AI tasks can now happen directly on-device. For example, in an AI-powered note-taking application, features such as summarization, translation, smart suggestions, and classification could potentially run locally without continuously communicating with remote servers.&lt;/p&gt;

&lt;p&gt;For heavier reasoning tasks, hybrid inference becomes important. Lightweight tasks can execute locally, while more computationally intensive operations can seamlessly fall back to cloud models when necessary. This creates a distributed intelligence model where computation is shared between the device and the cloud.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why This Matters for Emerging Markets
&lt;/h2&gt;

&lt;p&gt;Many cloud-first applications are designed around assumptions that often reflect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Silicon Valley-like infrastructure conditions,&lt;/li&gt;
&lt;li&gt;premium hardware,&lt;/li&gt;
&lt;li&gt;and stable high-speed internet connectivity.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, the ground reality is very different for billions of users around the world. Many people rely on affordable Android devices and unstable mobile networks.&lt;/p&gt;

&lt;p&gt;Hybrid architectures can help address this gap by enabling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;lower latency,&lt;/li&gt;
&lt;li&gt;reduced bandwidth usage,&lt;/li&gt;
&lt;li&gt;partially offline AI experiences,&lt;/li&gt;
&lt;li&gt;and better accessibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is particularly important for regions such as India, Africa, and Southeast Asia, where connectivity challenges still exist despite massive growth in smartphone adoption.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Bigger Industry Shift
&lt;/h2&gt;

&lt;p&gt;With the rapid growth of AI, the industry is gradually moving toward edge AI. Examples include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;on-device Gemini,&lt;/li&gt;
&lt;li&gt;Apple Intelligence,&lt;/li&gt;
&lt;li&gt;AI NPUs in smartphones,&lt;/li&gt;
&lt;li&gt;and local LLMs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The future of AI may not remain fully centralized. Instead, intelligence may become distributed across devices, edge systems, and cloud infrastructure working together collaboratively.&lt;/p&gt;




&lt;h2&gt;
  
  
  Critique and Challenges
&lt;/h2&gt;

&lt;p&gt;Like any architectural shift, this approach also comes with trade-offs.&lt;/p&gt;

&lt;p&gt;Local AI inference introduces the challenge of device fragmentation. Not all devices are capable of handling local AI workloads efficiently. On-device inference can also increase battery consumption and thermal load.&lt;/p&gt;

&lt;p&gt;Hybrid architectures are often more difficult to monitor, debug, and optimize compared to traditional centralized cloud systems.&lt;/p&gt;

&lt;p&gt;There is also the issue of vendor lock-in. Heavy dependence on tools such as  Firebase, Gemini, and the broader Google Cloud ecosystem could limit developer flexibility over time.&lt;/p&gt;

&lt;p&gt;Finally, local models still have computational limitations compared to larger cloud-hosted models.&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I believe the Firebase announcements at Google I/O 2026 were not simply about adding more AI capabilities. They reflected a broader shift in how modern applications may operate in the future: less dependent on permanent connectivity, more resilient at the edge, and increasingly capable of running intelligence closer to the user.&lt;/p&gt;

&lt;p&gt;The most important AI infrastructure trend may not be larger models alone, but the gradual movement of intelligence from centralized cloud systems toward user devices themselves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;If you liked the blog, consider sharing it among your peers and follow me on &lt;a href="https://www.linkedin.com/in/pravesh-sudha/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;, &lt;a href="https://x.com/praveshstwt" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; and subscribe to my &lt;a href="https://www.youtube.com/@pravesh-sudha" rel="noopener noreferrer"&gt;Youtube Channel&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>devchallenge</category>
      <category>googleiochallenge</category>
      <category>firebase</category>
      <category>ai</category>
    </item>
    <item>
      <title>🚀 Democratizing Frontier AI for Bharat: Gemma 4’s Edge Capabilities in Low-Resource Environments</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Mon, 18 May 2026 12:57:02 +0000</pubDate>
      <link>https://dev.to/pravesh_sudha_3c2b0c2b5e0/democratizing-frontier-ai-for-bharat-gemma-4s-edge-capabilities-in-low-resource-environments-b34</link>
      <guid>https://dev.to/pravesh_sudha_3c2b0c2b5e0/democratizing-frontier-ai-for-bharat-gemma-4s-edge-capabilities-in-low-resource-environments-b34</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/google-gemma-2026-05-06"&gt;Gemma 4 Challenge: Write About Gemma 4&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;How Google’s open-weight Gemma 4 models are shifting AI from a Silicon Valley luxury to a practical tool for India’s primary sector and developers working at the edge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Introduction: The Ground Reality
&lt;/h3&gt;

&lt;p&gt;India is set to solidify its position as the world’s most populous nation. Yet, even in 2025-26, &lt;strong&gt;around 43%&lt;/strong&gt; of its workforce remains employed in the primary sector — agriculture, animal husbandry, and allied activities. For millions of farmers in rural Rajasthan, Haryana, or Bihar, AI is still largely an abstract, distant concept.&lt;/p&gt;

&lt;p&gt;A mustard farmer in rural Rajasthan dealing with crop infestation or a livestock owner in a tier-3 town cannot rely on cloud-first AI. High latency, expensive USD-billed APIs, and poor or intermittent internet make frontier models inaccessible. This is where the paradigm must shift — from &lt;strong&gt;cloud-first&lt;/strong&gt; to &lt;strong&gt;edge-first&lt;/strong&gt; architecture.&lt;/p&gt;

&lt;p&gt;Google’s &lt;strong&gt;Gemma 4&lt;/strong&gt; family, particularly its edge-optimized models (E2B and E4B), represents a meaningful step in that direction.&lt;/p&gt;




&lt;h3&gt;
  
  
  Why Gemma 4 is Different for Bharat
&lt;/h3&gt;

&lt;p&gt;Gemma 4 stands out because of its intentional design for real-world constraints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Apache 2.0 license&lt;/strong&gt; — Fully open weights with commercial freedom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Edge-optimized models&lt;/strong&gt; — E2B (2.3 billion effective parameters) and E4B are built for phones, tablets, and single-board computers like Raspberry Pi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Native Multimodal&lt;/strong&gt; — Text + high-resolution images + audio (especially strong on E2B/E4B).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multilingual strength&lt;/strong&gt; — Pre-trained on over 140 languages, with strong performance on Indian languages and dialects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Long context&lt;/strong&gt; — Up to 128K tokens on edge models, enabling richer reasoning.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Getting started is surprisingly simple.&lt;/strong&gt; Download the free &lt;strong&gt;Google AI Edge Gallery&lt;/strong&gt; app (available on Android and iOS), select the Gemma 4 E2B model (~2.5 GB download), and you have a fully offline multimodal AI assistant on your phone. Once downloaded, it works without internet — text chat, image analysis, and voice input all run locally.&lt;/p&gt;




&lt;h3&gt;
  
  
  Real-World Performance: Benchmarks on Edge Hardware
&lt;/h3&gt;

&lt;p&gt;Performance numbers show why this is viable for low-resource settings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Model Size &amp;amp; Memory&lt;/strong&gt;: E2B quantized (INT4/Q4) has a ~2.58 GB footprint and runs in &lt;strong&gt;1.5–3 GB RAM&lt;/strong&gt; on devices, making it accessible on mid-range smartphones and Raspberry Pi.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Raspberry Pi 5 (16GB)&lt;/strong&gt;: Prefill &lt;strong&gt;133 tokens/sec&lt;/strong&gt;, Decode &lt;strong&gt;7.6–8 tokens/sec&lt;/strong&gt;, Peak memory &lt;strong&gt;~1.5 GB&lt;/strong&gt;. This is usable for batch advice, diagnostic reports, or non-real-time assistance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High-end Android (e.g., Samsung S26 Ultra)&lt;/strong&gt;: Decode speeds reach &lt;strong&gt;47–52 tokens/sec&lt;/strong&gt; on CPU and over &lt;strong&gt;50 tokens/sec&lt;/strong&gt; on GPU, with first-token latency under 2 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;iOS Devices&lt;/strong&gt;: Similar strong performance, especially on newer flagships.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These benchmarks prove that capable multimodal AI no longer requires expensive cloud GPUs or high-end laptops. A ₹8,000–15,000 smartphone or a ₹5,000–8,000 Raspberry Pi can now deliver practical intelligence offline.&lt;/p&gt;




&lt;h3&gt;
  
  
  On-the-Ground Use Cases: AI That Farmers Can Actually Use
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Visual Diagnostics&lt;/strong&gt;: A farmer points their phone at diseased leaves or livestock. Gemma 4 processes the image locally and suggests possible issues and remedies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Voice Interaction in Mother Tongue&lt;/strong&gt;: Thanks to strong multilingual capabilities, users can speak in Hindi, Rajasthani, Haryanvi, or other regional languages. The model understands intent without clumsy translation layers. “Kos-kos par badle paani, chaar kos par vaani” (The water changes every few miles, and the speech every fourth) — Gemma 4’s broad language coverage helps bridge this diversity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agentic Assistance&lt;/strong&gt;: Beyond simple Q&amp;amp;A, the model supports multi-step reasoning and tool use, making it suitable for practical workflows like “Analyze this crop image, suggest next steps considering common local practices.”&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market &amp;amp; Supply Chain&lt;/strong&gt;: Quick offline quality assessment of produce or basic price trend insights when connectivity returns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These capabilities turn a regular smartphone — now owned by a large majority of the population — into a personal Krishi advisor.&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.amazonaws.com%2Fuploads%2Farticles%2Fgm6okkwc5uv72naewilu.jpg" 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.amazonaws.com%2Fuploads%2Farticles%2Fgm6okkwc5uv72naewilu.jpg" alt="Father getting solution to their problem on phone" width="800" height="335"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  The DevOps Reckoning: From Cloud Comfort to Edge Reality
&lt;/h3&gt;

&lt;p&gt;As someone who has spent years in AI + DevOps, I’ve lived the cloud-native comfort zone: auto-scaling clusters, infinite compute, low-latency pipelines on AWS/GCP. That architecture collapses spectacularly when you try shipping frontier AI to rural India.&lt;/p&gt;

&lt;p&gt;Deploying Gemma 4 at the edge forces us to relearn core principles:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Orchestration: Hard Limits Over Auto-Scaling&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Swap EKS/GKE for lightweight solutions like &lt;strong&gt;K3s&lt;/strong&gt; or Docker Compose on edge nodes. Use cgroups and strict memory caps so multimodal inference doesn’t crash the host device (critical on phones or shared village Raspberry Pi hubs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Artifacts: Quantization as a First-Class CI/CD Stage&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The model itself becomes the build artifact. Integrate automated INT4/INT8 quantization (using tools like llama.cpp or LiteRT) into your pipelines. Ship deltas and LoRA adapters instead of full models.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Delivery: Pull-Based GitOps for Sporadic Networks&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Traditional push-based deployments fail offline. Design pull-based agents that sync during network windows — downloading only necessary updates or adapters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. New Observability&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Monitor battery drain, thermal throttling, and inference latency on diverse low-resource hardware. “High availability” now means the system works when the farmer needs it most — even with zero bars of signal.&lt;/p&gt;




&lt;h3&gt;
  
  
  Cloud vs Edge: Indian Context Comparison
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Cloud-First Models&lt;/th&gt;
&lt;th&gt;Gemma 4 Edge Models&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;Recurring API fees (USD)&lt;/td&gt;
&lt;td&gt;One-time hardware, free inference&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internet Requirement&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Fully offline capable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data Privacy&lt;/td&gt;
&lt;td&gt;Data leaves the device&lt;/td&gt;
&lt;td&gt;Stays local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Language Support&lt;/td&gt;
&lt;td&gt;English-first&lt;/td&gt;
&lt;td&gt;Strong 140+ languages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Latency&lt;/td&gt;
&lt;td&gt;Variable (network dependent)&lt;/td&gt;
&lt;td&gt;Near-instant local&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deployment Control&lt;/td&gt;
&lt;td&gt;Vendor locked&lt;/td&gt;
&lt;td&gt;Full ownership (Apache 2.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suitability for Rural Bharat&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Purpose-built&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h3&gt;
  
  
  Challenges We Must Address
&lt;/h3&gt;

&lt;p&gt;No technology is perfect. Key issues include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Risk of hallucinations in critical advice (needs verification loops or hybrid human-AI systems).&lt;/li&gt;
&lt;li&gt;Need for domain-specific fine-tuning on Indian crop/livestock datasets.&lt;/li&gt;
&lt;li&gt;Energy and thermal constraints on very low-end devices.&lt;/li&gt;
&lt;li&gt;Last-mile distribution and digital literacy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are engineering + ecosystem problems we can solve together.&lt;/p&gt;




&lt;h3&gt;
  
  
  Conclusion: Engineering for the 43%
&lt;/h3&gt;

&lt;p&gt;The true frontier of AI is not in multi-million dollar clusters in Silicon Valley. It is being forged at the rugged edge — in the hands of farmers, extension workers, and developers who understand local realities.&lt;/p&gt;

&lt;p&gt;Gemma 4 won’t solve every problem overnight, but it lowers the barrier dramatically. By embracing edge-first design with open models, we shift DevOps responsibility from managing cloud bills to solving real constraints: every kilobyte, unpredictable networks, and diverse hardware.&lt;/p&gt;

&lt;p&gt;It’s time to stop building only for the comfortable few. Frontier engineering should level the playing field for Bharat.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Let’s build for the 43%.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you find the Article useful, make sure to share it among your socials, tagging me on &lt;a href="https://www.linkedin.com/in/pravesh-sudha/" rel="noopener noreferrer"&gt;Linkedin&lt;/a&gt;, &lt;a href="https://x.com/praveshstwt" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt;. &lt;br&gt;
Checkout my &lt;a href="https://www.youtube.com/@pravesh-sudha" rel="noopener noreferrer"&gt;Youtube Channel&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gemmachallenge</category>
      <category>gemma</category>
      <category>ai</category>
    </item>
    <item>
      <title>🚀 Kubernetes for Beginners: Deploying an Nginx–Node–Redis Application | Episode 1</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Fri, 03 Apr 2026 12:55:17 +0000</pubDate>
      <link>https://dev.to/aws-builders/kubernetes-for-beginners-deploying-an-nginx-node-redis-application-1o7h</link>
      <guid>https://dev.to/aws-builders/kubernetes-for-beginners-deploying-an-nginx-node-redis-application-1o7h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Understanding Services, ConfigMaps, Deployments, and health checks with my WAY!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hola Amigos! 👋&lt;/p&gt;

&lt;p&gt;Today, we are embarking on a brand new series: &lt;strong&gt;K8s with Pravesh&lt;/strong&gt; 🚀 — where we’ll break down Kubernetes, understand what it really is, and more importantly, how you can &lt;em&gt;actually&lt;/em&gt; use it in a practical, no-BS way.&lt;/p&gt;

&lt;p&gt;In today’s blog, we’ll dive into the fundamentals — &lt;strong&gt;Deployments, Services, and ConfigMaps&lt;/strong&gt; — and use them to deploy a &lt;strong&gt;three-tier application on Minikube&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now you might be thinking… &lt;em&gt;“What’s new here? There are already thousands of blogs doing the same thing.”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;And honestly, you’re not wrong.&lt;/p&gt;

&lt;p&gt;But hold your horses for a second 🐎&lt;/p&gt;

&lt;p&gt;This isn’t just another “apply this YAML and it works” kind of tutorial. We’re going to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Understand &lt;strong&gt;what’s really happening under the hood&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Debug real issues (yes, the ones that &lt;em&gt;actually&lt;/em&gt; happen)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And build intuition so you don’t just run Kubernetes… you &lt;strong&gt;get it&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So let’s dive in. 🔥&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Pre-Requisites
&lt;/h2&gt;

&lt;p&gt;Before we dive deep, there are a couple of things you need to have set up. Nothing fancy — just the essentials to get your Kubernetes playground up and running.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔹 Docker / Docker Desktop
&lt;/h3&gt;

&lt;p&gt;We’ll be running Minikube using Docker, so make sure you have Docker installed on your system.&lt;/p&gt;

&lt;p&gt;👉 Install it from here: &lt;a href="https://docs.docker.com/get-started/get-docker/" rel="noopener noreferrer"&gt;https://docs.docker.com/get-started/get-docker/&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🔹 Minikube
&lt;/h3&gt;

&lt;p&gt;Think of Minikube as your &lt;strong&gt;personal Kubernetes cluster&lt;/strong&gt; — lightweight, local, and perfect for experimenting and learning all the cool stuff without needing a cloud setup.&lt;/p&gt;

&lt;p&gt;👉 Download it from here: &lt;a href="https://minikube.sigs.k8s.io/docs/start/?arch=%2Fmacos%2Farm64%2Fstable%2Fbinary+download" rel="noopener noreferrer"&gt;https://minikube.sigs.k8s.io/docs/start/?arch=%2Fmacos%2Farm64%2Fstable%2Fbinary+download&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎥 Practical Demonstration
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/ZYlRwMf4lYA"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  🤔 What is Kubernetes (K8s)?
&lt;/h2&gt;

&lt;p&gt;At its core, &lt;strong&gt;Kubernetes is a container orchestration tool&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Now that sounds fancy, but let’s simplify it a bit.&lt;/p&gt;

&lt;p&gt;Think of Kubernetes as a &lt;strong&gt;Head Chef in a restaurant&lt;/strong&gt; 👨‍🍳 It makes sure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Everyone is doing their job properly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Work is flowing smoothly&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And if something breaks… it steps in and fixes it&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the &lt;em&gt;layman definition&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Real Meaning
&lt;/h3&gt;

&lt;p&gt;In technical terms, Kubernetes is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Managing containers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scaling them&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ensuring they are always running&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Handling communication between them&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can think of it as an advanced version of Docker Compose — but built for &lt;strong&gt;production-grade systems&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Smallest Unit: Pod
&lt;/h3&gt;

&lt;p&gt;In Kubernetes, the smallest deployable unit is a &lt;strong&gt;Pod&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;👉 A Pod is basically a wrapper around your container(s)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It can run &lt;strong&gt;one or more containers&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;These containers share:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Network&lt;/li&gt;
&lt;li&gt;  Storage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here’s the thing…&lt;/p&gt;

&lt;p&gt;Managing Pods manually? 😵‍💫 Not a great idea.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter Deployments
&lt;/h3&gt;

&lt;p&gt;To solve that, we have &lt;strong&gt;Deployments&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A Deployment is like a &lt;strong&gt;blueprint for your Pods&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Container image&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Number of replicas&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ports&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Volumes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other configurations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And Kubernetes takes care of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Creating Pods&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scaling them&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replacing them if they crash&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💥 Much easier to manage.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Do Pods Talk to Each Other?
&lt;/h3&gt;

&lt;p&gt;Back to our restaurant analogy 🍽️&lt;/p&gt;

&lt;p&gt;The waiter needs to communicate with the chef, right?&lt;/p&gt;

&lt;p&gt;But in Kubernetes… 👉 Pods don’t automatically talk to each other&lt;/p&gt;

&lt;p&gt;We need something in between.&lt;/p&gt;

&lt;h3&gt;
  
  
  Services: The Communication Bridge
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Services&lt;/strong&gt; act as a bridge between Pods.&lt;/p&gt;

&lt;p&gt;They provide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Stable networking&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Internal DNS&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Load balancing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are 3 main types:&lt;/p&gt;

&lt;h3&gt;
  
  
  🔹 ClusterIP
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Default type&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Used for &lt;strong&gt;internal communication only&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Not accessible from outside the cluster&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔹 NodePort
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Exposes the service on a &lt;strong&gt;specific port on the node&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Accessible from outside using:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;Node-IP&amp;gt;:&amp;lt;Port&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  🔹 LoadBalancer
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Exposes the app to the &lt;strong&gt;outside world&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commonly used in cloud environments (AWS, GCP, etc.)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ConfigMaps: Handling Custom Configurations
&lt;/h3&gt;

&lt;p&gt;Back to the restaurant…&lt;/p&gt;

&lt;p&gt;Imagine a customer walks in and says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“I want a Caffè macchiato, with a little bit of soy, enough to make me go OH BOY!”&lt;/em&gt; — Kevin Hart fans, you know 😄&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.amazonaws.com%2Fuploads%2Farticles%2F23vp6luxmn3hio126cwt.gif" 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.amazonaws.com%2Fuploads%2Farticles%2F23vp6luxmn3hio126cwt.gif" alt=" " width="286" height="230"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Handling custom requests manually can get messy…&lt;/p&gt;

&lt;p&gt;But in Kubernetes, we have &lt;strong&gt;ConfigMaps&lt;/strong&gt; for this.&lt;/p&gt;

&lt;p&gt;👉 ConfigMaps allow you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Store &lt;strong&gt;non-confidential data&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use it inside your applications&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keep configs separate from your code&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For sensitive data? 👉 Use &lt;strong&gt;Secrets&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  YAML: The Language of Kubernetes
&lt;/h3&gt;

&lt;p&gt;All resources in Kubernetes are defined using &lt;strong&gt;YAML files&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You describe:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;What you want&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And Kubernetes makes it happen&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to explore more, check out the official docs: 👉 &lt;a href="https://kubernetes.io/docs/setup/" rel="noopener noreferrer"&gt;https://kubernetes.io/docs/setup/&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ Practical Demonstration
&lt;/h2&gt;

&lt;p&gt;Enough with the theory — now let’s get our hands dirty 🔥&lt;/p&gt;

&lt;p&gt;So far, we’ve covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deployments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Services&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ConfigMaps&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And to bring all of this together, we’ll deploy a &lt;strong&gt;three-tier application (Nginx–Node–Redis)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I’ve actually used this same app in one of my earlier projects to demonstrate CI/CD workflows with GitHub Actions and Terraform. If you’re curious, check it out here: 👉 &lt;a href="https://blog.praveshsudha.com/cicd-for-terraform-with-github-actions-deploying-a-nodejs-redis-app-on-aws" rel="noopener noreferrer"&gt;https://blog.praveshsudha.com/cicd-for-terraform-with-github-actions-deploying-a-nodejs-redis-app-on-aws&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Clone the Project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Pravesh-Sudha/nginx-node-redis.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the project in your favorite editor (VS Code works great).&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the App
&lt;/h3&gt;

&lt;p&gt;This is a simple Node.js application that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Displays a &lt;strong&gt;request counter&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Increments the count on every refresh&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Stores data in &lt;strong&gt;Redis&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Uses &lt;strong&gt;Nginx as a reverse proxy&lt;/strong&gt; (serving on port 80 instead of 5000)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Run with Docker Compose
&lt;/h3&gt;

&lt;p&gt;Before jumping into Kubernetes, let’s run it locally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker-compose up &lt;span class="nt"&gt;--build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fxsm9mddjlvrlcl2bfmzc.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.amazonaws.com%2Fuploads%2Farticles%2Fxsm9mddjlvrlcl2bfmzc.png" alt=" " width="799" height="383"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Make sure Docker Desktop is installed and running.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You should see logs in your terminal and the app running in your browser.&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.amazonaws.com%2Fuploads%2Farticles%2Fj3918cxhv9n6dwywn505.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.amazonaws.com%2Fuploads%2Farticles%2Fj3918cxhv9n6dwywn505.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Ctrl + C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Step 3: Move to Kubernetes
&lt;/h3&gt;

&lt;p&gt;Now comes the interesting part.&lt;/p&gt;

&lt;p&gt;Inside the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;nginx-node-redis/kube-config/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You’ll find three directories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;nginx/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;node/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;redis/&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each contains:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Deployment YAML&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service YAML&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📦 Nginx Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.14.2&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;

        &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config-volume&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/nginx.conf&lt;/span&gt;
          &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx.conf&lt;/span&gt;

      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config-volume&lt;/span&gt;
        &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Note on AI &amp;amp; YAML
&lt;/h3&gt;

&lt;p&gt;The best thing about AI? 👉 You can generate YAML files instantly.&lt;/p&gt;

&lt;p&gt;But what happens when things break?&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;fundamentals matter&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let’s break this down 👇&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the Deployment
&lt;/h3&gt;

&lt;h3&gt;
  
  
  1. API Version &amp;amp; Kind
&lt;/h3&gt;

&lt;p&gt;Defines what resource we are creating:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Labels (IMPORTANT)
&lt;/h3&gt;

&lt;p&gt;Labels appear in three places — and each has a role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;metadata.labels&lt;/strong&gt; → tagging the Deployment&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;spec.selector.matchLabels&lt;/strong&gt; → tells Deployment which Pods to manage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;template.metadata.labels&lt;/strong&gt; → applied to Pods (used by Services)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This is how Kubernetes “connects” resources.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Container Spec
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.14.2&lt;/span&gt;
&lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Image&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Port&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. ConfigMap Mount
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config-volume&lt;/span&gt;
  &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/nginx.conf&lt;/span&gt;
  &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx.conf&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 This mounts your custom Nginx config into the container.&lt;/p&gt;

&lt;h3&gt;
  
  
  🌐 Nginx Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;We use &lt;strong&gt;ClusterIP&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Selector matches:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 This connects the Service to Pods.&lt;/p&gt;

&lt;h2&gt;
  
  
  ⚙️ Nginx ConfigMap
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;nginx.conf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;events {}&lt;/span&gt;

    &lt;span class="s"&gt;http {&lt;/span&gt;
      &lt;span class="s"&gt;upstream loadbalancer {&lt;/span&gt;
        &lt;span class="s"&gt;server node-service:5000;&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;

      &lt;span class="s"&gt;server {&lt;/span&gt;
        &lt;span class="s"&gt;listen 80;&lt;/span&gt;

        &lt;span class="s"&gt;location / {&lt;/span&gt;
          &lt;span class="s"&gt;proxy_pass http://loadbalancer;&lt;/span&gt;
          &lt;span class="s"&gt;proxy_set_header Host $host;&lt;/span&gt;
          &lt;span class="s"&gt;proxy_set_header X-Real-IP $remote_addr;&lt;/span&gt;
          &lt;span class="s"&gt;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;

        &lt;span class="s"&gt;location = /favicon.ico {&lt;/span&gt;
          &lt;span class="s"&gt;log_not_found off;&lt;/span&gt;
          &lt;span class="s"&gt;access_log off;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 Here we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Override default Nginx config&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Route traffic to:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node-service:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Deploy to Minikube
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start Minikube&lt;/span&gt;
minikube start

&lt;span class="c"&gt;# Go to config directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;nginx-node-redis/kube-config/

&lt;span class="c"&gt;# Deploy Redis&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;redis/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deploy.yml &lt;span class="nt"&gt;-f&lt;/span&gt; svc.yml
&lt;span class="nb"&gt;cd&lt;/span&gt; ..

&lt;span class="c"&gt;# Deploy Node&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;node/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deploy.yaml &lt;span class="nt"&gt;-f&lt;/span&gt; svc.yml
&lt;span class="nb"&gt;cd&lt;/span&gt; ..

&lt;span class="c"&gt;# Deploy Nginx&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;nginx/ &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deploy.yml &lt;span class="nt"&gt;-f&lt;/span&gt; svc.yml &lt;span class="nt"&gt;-f&lt;/span&gt; configmap.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fv8pe8ao9b1l8pbcru895.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.amazonaws.com%2Fuploads%2Farticles%2Fv8pe8ao9b1l8pbcru895.png" alt=" " width="800" height="570"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Wait for Pods
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-w&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait until all pods are:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fqorxloe9ja22cxz2n8rt.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.amazonaws.com%2Fuploads%2Farticles%2Fqorxloe9ja22cxz2n8rt.png" alt=" " width="800" height="206"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Access the App
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube service nginx-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 This opens your app in the browser — now running on Kubernetes 🎉&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.amazonaws.com%2Fuploads%2Farticles%2Fu8rot9k6vz8dwj9znubz.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.amazonaws.com%2Fuploads%2Farticles%2Fu8rot9k6vz8dwj9znubz.png" alt=" " width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-Healing in Action
&lt;/h3&gt;

&lt;p&gt;Here’s where Kubernetes shines.&lt;/p&gt;

&lt;p&gt;Let’s break something 😈&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pod &amp;lt;pod-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 You’ll see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A new pod automatically created&lt;/li&gt;
&lt;/ul&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.amazonaws.com%2Fuploads%2Farticles%2F9qzapaw42qkdif28k127.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.amazonaws.com%2Fuploads%2Farticles%2F9qzapaw42qkdif28k127.png" alt=" " width="799" height="445"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 What just happened?
&lt;/h3&gt;

&lt;p&gt;Kubernetes ensures:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Actual state = Desired state”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Even if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Delete a pod&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Crash a container&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Kubernetes will bring it back&lt;/p&gt;




&lt;h2&gt;
  
  
  🔍 What’s Happening Under the Hood?
&lt;/h2&gt;

&lt;p&gt;Now that everything is up and running, let’s take a step back and understand &lt;strong&gt;how things are actually working behind the scenes&lt;/strong&gt; 👇&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Accessing the Application
&lt;/h3&gt;

&lt;p&gt;When you run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube service nginx-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fho6psep81sfzatt6z1qn.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.amazonaws.com%2Fuploads%2Farticles%2Fho6psep81sfzatt6z1qn.png" alt=" " width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;👉 Minikube exposes your service and gives you a &lt;strong&gt;URL with a port&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Request Hits Nginx Service
&lt;/h3&gt;

&lt;p&gt;Once you hit that URL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;strong&gt;Nginx Service&lt;/strong&gt; receives the request&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It looks at its selector:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And forwards the request to all matching &lt;strong&gt;Nginx Pods&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Inside the Nginx Pod
&lt;/h3&gt;

&lt;p&gt;Inside the pod:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Nginx uses the &lt;strong&gt;custom config (via ConfigMap)&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The request is proxied to:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node-service:5000
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Node Service Load Balancing
&lt;/h3&gt;

&lt;p&gt;Now the interesting part 👀&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;node-service&lt;/code&gt; is a &lt;strong&gt;ClusterIP Service&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It has multiple pods (replicas = 3)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;👉 Kubernetes automatically distributes traffic:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node-service
   ↓
 ┌──────────┬──────────┬──────────┐
 │ node-pod1│ node-pod2│ node-pod3│
 └──────────┴──────────┴──────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Node App Talks to Redis
&lt;/h3&gt;

&lt;p&gt;Inside your Node app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;It connects to:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;redis-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Stores:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Request count&lt;/li&gt;
&lt;li&gt;  Cache data&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  6. Response Flow
&lt;/h3&gt;

&lt;p&gt;Finally, the response travels back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Redis → Node → Nginx → Browser
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🎉 And you see the updated request count&lt;/p&gt;

&lt;h3&gt;
  
  
  🧠 Key Insight
&lt;/h3&gt;

&lt;p&gt;Notice something important here…&lt;/p&gt;

&lt;p&gt;👉 We never used a single IP address.&lt;/p&gt;

&lt;p&gt;Everything works using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service names&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Internal DNS&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Labels &amp;amp; selectors&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is called &lt;strong&gt;Service Discovery&lt;/strong&gt; — one of the most powerful features of Kubernetes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scaling Made Easy
&lt;/h3&gt;

&lt;p&gt;Want more traffic handling capacity?&lt;/p&gt;

&lt;p&gt;Just update:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 Increase or decrease as needed&lt;/p&gt;

&lt;p&gt;👉 No changes required anywhere else&lt;/p&gt;

&lt;p&gt;Kubernetes handles the rest&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleanup
&lt;/h3&gt;

&lt;p&gt;Once you’re done experimenting, you can delete the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube delete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fec5ugnbwvij1eyyg8kwk.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.amazonaws.com%2Fuploads%2Farticles%2Fec5ugnbwvij1eyyg8kwk.png" alt=" " width="800" height="125"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Conclusion
&lt;/h2&gt;

&lt;p&gt;And that’s a wrap for this one! 🚀&lt;/p&gt;

&lt;p&gt;In this blog, we didn’t just deploy an application on Kubernetes — we actually &lt;strong&gt;understood what’s happening behind the scenes&lt;/strong&gt;. From Deployments and Services to ConfigMaps and internal service discovery, you now have a solid foundation to start building real-world K8s projects.&lt;/p&gt;

&lt;p&gt;More importantly, you saw how:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kubernetes replaces static setups like Docker Compose with &lt;strong&gt;dynamic, scalable systems&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Services enable seamless communication without worrying about IPs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;And how the system &lt;strong&gt;self-heals&lt;/strong&gt; to match the desired state&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is just the beginning of the &lt;strong&gt;K8s with Pravesh&lt;/strong&gt; series. In the upcoming blogs, we’ll go deeper into more advanced concepts and build even more powerful systems 💥&lt;/p&gt;

&lt;h3&gt;
  
  
  🔗 Let’s Connect
&lt;/h3&gt;

&lt;p&gt;If you found this helpful, feel free to connect with me and follow along for more DevOps and Kubernetes content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;💼 LinkedIn: &lt;a href="https://www.linkedin.com/in/pravesh-sudha" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/pravesh-sudha&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📝 Blog: &lt;a href="https://blog.praveshsudha.com" rel="noopener noreferrer"&gt;https://blog.praveshsudha.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;💻 GitHub: &lt;a href="https://github.com/Pravesh-Sudha" rel="noopener noreferrer"&gt;https://github.com/Pravesh-Sudha&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have any questions, got stuck somewhere, or just want to discuss ideas — my DMs are always open 🙌&lt;/p&gt;

&lt;p&gt;Until next time… Keep building, keep learning, and keep shipping 🚀&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>docker</category>
      <category>beginners</category>
    </item>
    <item>
      <title>🔥 𝗜 𝗮𝗺 𝗻𝗼𝘄 𝗮𝗻 𝗜𝗕𝗠 𝗖𝗵𝗮𝗺𝗽𝗶𝗼𝗻 / 𝗛𝗮𝘀𝗵𝗶𝗰𝗼𝗿𝗽 𝗔𝗺𝗯𝗮𝘀𝘀𝗮𝗱𝗼𝗿 𝗕𝗔𝗕𝗬!!! 🔥

Excited to announce that I am officially recognised as an IBM Champion earlier known as Hashicorp Ambassador this year.</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Thu, 26 Feb 2026 04:58:00 +0000</pubDate>
      <link>https://dev.to/pravesh_sudha_3c2b0c2b5e0/-excited-to-announce-that-i-am-2a7l</link>
      <guid>https://dev.to/pravesh_sudha_3c2b0c2b5e0/-excited-to-announce-that-i-am-2a7l</guid>
      <description></description>
    </item>
    <item>
      <title>🚀 Building an AI-Powered CI/CD Copilot with Jenkins and AWS Lambda</title>
      <dc:creator>Pravesh Sudha</dc:creator>
      <pubDate>Tue, 24 Feb 2026 18:15:10 +0000</pubDate>
      <link>https://dev.to/aws-builders/building-an-ai-powered-cicd-copilot-with-jenkins-and-aws-lambda-4k8l</link>
      <guid>https://dev.to/aws-builders/building-an-ai-powered-cicd-copilot-with-jenkins-and-aws-lambda-4k8l</guid>
      <description>&lt;h2&gt;
  
  
  💡 Introduction
&lt;/h2&gt;

&lt;p&gt;Hey folks, welcome to the world of Agentic Tools and DevOps.&lt;/p&gt;

&lt;p&gt;Today, we’re diving into CI/CD pipelines and exploring how we can debug them efficiently and almost instantly using AI. In this project, we’ll build an AI-powered CI/CD Copilot where&amp;nbsp;&lt;strong&gt;AWS Lambda&lt;/strong&gt;&amp;nbsp;serves as the core logic layer. This Lambda function will interact with the Google Gemini API to analyze pipeline failures and help us debug them intelligently.&lt;/p&gt;

&lt;p&gt;The goal of this project is not just to integrate AI into a CI/CD workflow, but to help you understand how to build your own AI agent from scratch — one that can assist in real-world DevOps scenarios.&lt;/p&gt;

&lt;p&gt;So, without further ado, let’s get started.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we begin, make sure you have the following requirements in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Docker &amp;amp; Docker Hub account&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We will run parts of this project inside Docker containers. Later, we’ll push our custom image to Docker Hub, so make sure you have both Docker installed and a Docker Hub account ready.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Jenkins (Our CI/CD Tool)&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We’ll use Jenkins for demonstration purposes. You can either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Run Jenkins as a Docker container, or&lt;/li&gt;
&lt;li&gt;  Install it directly from the official website.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;Terraform&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
We will provision our infrastructure — including the Gemini API key (stored securely) and the AWS Lambda function — using Terraform.&lt;/p&gt;

&lt;p&gt;Make sure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Terraform CLI is installed&lt;/li&gt;
&lt;li&gt;  Your AWS credentials are configured&lt;/li&gt;
&lt;li&gt;  The IAM user has permissions for&amp;nbsp;&lt;strong&gt;AWS Lambda&lt;/strong&gt;&amp;nbsp;and&amp;nbsp;&lt;strong&gt;AWS Secrets Manager&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re new to Terraform setup, you can follow this guide:&lt;br&gt;&lt;br&gt;
👉&amp;nbsp;&lt;a href="https://blog.praveshsudha.com/getting-started-with-terraform-a-beginners-guide#heading-step-1-install-the-aws-cli" rel="noopener noreferrer"&gt;https://blog.praveshsudha.com/getting-started-with-terraform-a-beginners-guide#heading-step-1-install-the-aws-cli&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  🎥 Youtube Demonstration
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/b7_k_auDUEo"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 How It Works
&lt;/h2&gt;

&lt;p&gt;The complete source code for this project is available in this GitHub repository:&lt;br&gt;&lt;br&gt;
👉&amp;nbsp;&lt;a href="https://github.com/Pravesh-Sudha/ai-devops-agent" rel="noopener noreferrer"&gt;https://github.com/Pravesh-Sudha/ai-devops-agent&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Navigate to the&amp;nbsp;&lt;code&gt;cicd-copilot&lt;/code&gt;&amp;nbsp;directory to follow along.&lt;/p&gt;

&lt;p&gt;If you’ve been following my work, you might recognize this project. I originally used this same&amp;nbsp;&lt;strong&gt;Node.js Book Reader application&lt;/strong&gt;&amp;nbsp;to demonstrate how Docker works with Node.js. For this AI-powered CI/CD Copilot, I’ve made specific modifications — particularly in the&amp;nbsp;&lt;strong&gt;Jenkinsfile&lt;/strong&gt;&amp;nbsp;and the&amp;nbsp;&lt;code&gt;terra-config&lt;/code&gt;&amp;nbsp;directory.&lt;/p&gt;

&lt;p&gt;Inside the&amp;nbsp;&lt;code&gt;terra-config&lt;/code&gt;&amp;nbsp;directory, you’ll find:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;main.tf&lt;/strong&gt;&amp;nbsp;– Provisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  AWS Lambda function&lt;/li&gt;
&lt;li&gt;  AWS Secrets Manager secret (to securely store the Gemini API key)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;lambda.zip&lt;/strong&gt;&amp;nbsp;– The packaged Lambda deployment artifact (zipped&amp;nbsp;&lt;code&gt;lambda_function.py&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;lambda_function.py&lt;/strong&gt;&amp;nbsp;– The core of this project.&lt;br&gt;&lt;br&gt;
This file contains the AI agent logic and the structured prompt sent to the Gemini API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;iam.tf&lt;/strong&gt;&amp;nbsp;– Defines the IAM roles and permissions required for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  AWS Lambda&lt;/li&gt;
&lt;li&gt;  AWS Secrets Manager&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Architecture Overview
&lt;/h3&gt;

&lt;p&gt;The core idea behind this project is simple:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Jenkins detects a pipeline failure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It collects contextual information (stage name, build ID, logs).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It sends that data to AWS Lambda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambda calls the Gemini API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gemini analyzes the logs and returns structured debugging insights.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Payload Sent to Lambda
&lt;/h3&gt;

&lt;p&gt;The Lambda function expects a JSON payload in the following format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;# Name of the stage where the pipeline failed
&lt;/span&gt;   &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;            &lt;span class="c1"&gt;# Job name (e.g., cicd-copilot)
&lt;/span&gt;   &lt;span class="n"&gt;build_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;build_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;# Build ID number (e.g., 1, 2, 3)
&lt;/span&gt;   &lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="n"&gt;logs&lt;/span&gt;           &lt;span class="c1"&gt;# Last 200 lines of failure logs
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structured input allows the AI agent to understand the pipeline context before analyzing the logs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prompt Sent to Gemini API
&lt;/h3&gt;

&lt;p&gt;Inside the Lambda function, we make a POST request to the Gemini API with the following structured prompt:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;You&lt;/span&gt; &lt;span class="n"&gt;are&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;senior&lt;/span&gt; &lt;span class="n"&gt;CI&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;CD&lt;/span&gt; &lt;span class="n"&gt;Copilot&lt;/span&gt; &lt;span class="n"&gt;specialized&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;Jenkins&lt;/span&gt; &lt;span class="n"&gt;pipelines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;Pipeline&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Stage&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;Expected&lt;/span&gt; &lt;span class="n"&gt;outcome&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Build&lt;/span&gt; &lt;span class="n"&gt;an&lt;/span&gt; &lt;span class="n"&gt;artifact&lt;/span&gt; &lt;span class="n"&gt;usable&lt;/span&gt; &lt;span class="n"&gt;by&lt;/span&gt; &lt;span class="n"&gt;later&lt;/span&gt; &lt;span class="n"&gt;stages&lt;/span&gt;

&lt;span class="n"&gt;Your&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mf"&gt;1.&lt;/span&gt; &lt;span class="n"&gt;Identify&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;failure&lt;/span&gt; &lt;span class="nf"&gt;category &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;runtime&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;infra&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;dependency&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mf"&gt;2.&lt;/span&gt; &lt;span class="n"&gt;Identify&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="n"&gt;most&lt;/span&gt; &lt;span class="n"&gt;likely&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="n"&gt;cause&lt;/span&gt;
&lt;span class="mf"&gt;3.&lt;/span&gt; &lt;span class="n"&gt;Provide&lt;/span&gt; &lt;span class="n"&gt;actionable&lt;/span&gt; &lt;span class="n"&gt;fixes&lt;/span&gt;
&lt;span class="mf"&gt;4.&lt;/span&gt; &lt;span class="n"&gt;Suggest&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt; &lt;span class="n"&gt;ONLY&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;clearly&lt;/span&gt; &lt;span class="n"&gt;inferable&lt;/span&gt;

&lt;span class="n"&gt;Respond&lt;/span&gt; &lt;span class="n"&gt;ONLY&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;valid&lt;/span&gt; &lt;span class="n"&gt;JSON&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;{{&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failure_category&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;root_cause&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;actionable_fixes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;suggested_patch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fix&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
  &lt;span class="p"&gt;}}&lt;/span&gt;
&lt;span class="p"&gt;}}&lt;/span&gt;

&lt;span class="n"&gt;Logs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The prompt dynamically injects two key variables:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;{stage}&lt;/code&gt;&amp;nbsp;– The pipeline stage name&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;{logs}&lt;/code&gt;&amp;nbsp;– The failure logs&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’d like to explore the full Lambda implementation, you can view it here:&lt;br&gt;&lt;br&gt;
👉&amp;nbsp;&lt;a href="https://github.com/Pravesh-Sudha/ai-devops-agent/blob/main/cicd-copilot/terra-config/lambda_function.py" rel="noopener noreferrer"&gt;https://github.com/Pravesh-Sudha/ai-devops-agent/blob/main/cicd-copilot/terra-config/lambda_function.py&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How It Integrates with Jenkins
&lt;/h3&gt;

&lt;p&gt;You might be wondering — how exactly does this connect with Jenkins?&lt;/p&gt;

&lt;p&gt;Inside the&amp;nbsp;&lt;code&gt;Jenkinsfile&lt;/code&gt;, each stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Sets an environment variable for the stage name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Redirects command output (in case of failure) into a&amp;nbsp;&lt;code&gt;LOG_FILE&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any stage fails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The&amp;nbsp;&lt;code&gt;post { failure { ... } }&lt;/code&gt;&amp;nbsp;block is triggered.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Jenkins constructs the JSON payload.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;It invokes the AWS Lambda function.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The AI-generated failure analysis is printed directly into the Jenkins console output.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you instant, structured debugging assistance right inside your CI/CD pipeline.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to Integrate This in Your Own Workspace
&lt;/h3&gt;

&lt;p&gt;To replicate this approach in your own pipeline:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Append log redirection to each command:&lt;br&gt;
&lt;/p&gt;

&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt; 2&amp;gt;&amp;amp;1
&lt;/code&gt;&lt;/pre&gt;




&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Define an environment variable for the stage name.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Provision:&lt;/p&gt;&lt;/li&gt;

&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   AWS Lambda

*   IAM roles

*   Secrets Manager (for the Gemini API key)  
    using Terraform.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt; Add a&amp;nbsp;&lt;code&gt;post failure&lt;/code&gt;&amp;nbsp;block in your Jenkinsfile to invoke the Lambda function with the structured JSON payload.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once configured, your CI/CD pipeline becomes AI-assisted — capable of analyzing its own failures and suggesting actionable fixes.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Practical Demonstration
&lt;/h2&gt;

&lt;p&gt;Enough with the theory — let’s see this in action.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Fork and Clone the Repository
&lt;/h3&gt;

&lt;p&gt;First, head over to the GitHub repository and&amp;nbsp;&lt;strong&gt;fork it under your own username&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
You’ll be intentionally modifying the code later to trigger pipeline failures, so forking is important.&lt;/p&gt;

&lt;p&gt;After forking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/your-username/ai-devops-agent.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ai-devops-agent/cicd-copilot/terra-config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Initialize Terraform
&lt;/h3&gt;

&lt;p&gt;Inside the&amp;nbsp;&lt;code&gt;terra-config&lt;/code&gt;&amp;nbsp;directory, initialize Terraform:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Generate Your Gemini API Key
&lt;/h3&gt;

&lt;p&gt;To provision the infrastructure, you’ll need a&amp;nbsp;&lt;strong&gt;GEMINI_API_KEY&lt;/strong&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to&amp;nbsp;&lt;strong&gt;Google AI Studio&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Log in with your Google account&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Navigate to the&amp;nbsp;&lt;strong&gt;API&lt;/strong&gt;&amp;nbsp;section&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;Create API Key&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Give it a name and generate the key&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Store it securely&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now, apply the Terraform configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform apply &lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini_api_key=&amp;lt;Paste-your-key-here&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;--auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Ftbi7vwz8dy1h7g26lolh.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.amazonaws.com%2Fuploads%2Farticles%2Ftbi7vwz8dy1h7g26lolh.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ Make sure the configured AWS IAM user has the required permissions (Lambda and Secrets Manager access), as mentioned in the prerequisites section.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Once completed, your infrastructure (Lambda function + IAM roles + Secret) will be up and running.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Configure Jenkins Pipeline
&lt;/h3&gt;

&lt;p&gt;Open your Jenkins dashboard (usually running on&amp;nbsp;&lt;code&gt;http://localhost:8080&lt;/code&gt;).&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;Create New Item&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select&amp;nbsp;&lt;strong&gt;Pipeline&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name it:&amp;nbsp;&lt;code&gt;cicd-copilot&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choose&amp;nbsp;&lt;strong&gt;Pipeline script from SCM&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Configure the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SCM:&lt;/strong&gt;&amp;nbsp;Git&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository URL:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;https://github.com/your-username/ai-devops-agent&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Branch Specifier:&lt;/strong&gt;&amp;nbsp;&lt;code&gt;main&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Script Path:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;code&gt;cicd-copilot/Jenkinsfile&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Click&amp;nbsp;&lt;strong&gt;Save&lt;/strong&gt;.&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.amazonaws.com%2Fuploads%2Farticles%2Fkp6mo53plbgxpl6jal9x.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.amazonaws.com%2Fuploads%2Farticles%2Fkp6mo53plbgxpl6jal9x.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Install Required Jenkins Plugins
&lt;/h3&gt;

&lt;p&gt;Navigate to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manage Jenkins → Plugins&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Install the following plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Docker&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker Pipeline&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker Commons&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 6: Add Docker to Jenkins PATH
&lt;/h3&gt;

&lt;p&gt;Ensure Docker is accessible inside Jenkins.&lt;/p&gt;

&lt;p&gt;In your terminal, run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which docker
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the output path.&lt;/p&gt;

&lt;p&gt;Now go to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manage Jenkins → System → Global Properties&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Append the copied path to the existing PATH variable using&amp;nbsp;&lt;code&gt;:&lt;/code&gt;&amp;nbsp;as a separator. Save the configuration.&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.amazonaws.com%2Fuploads%2Farticles%2F33ilfvp62i8gxowi1p35.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.amazonaws.com%2Fuploads%2Farticles%2F33ilfvp62i8gxowi1p35.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Add Docker Hub Credentials
&lt;/h3&gt;

&lt;p&gt;Navigate to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manage Jenkins → Credentials&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Add a new credential:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;*   Kind:&amp;nbsp;**Username with password**

*   Username: Your Docker Hub username

*   Password: Your Docker Hub password

*   ID:&amp;nbsp;`docker-cred`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Save it.&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.amazonaws.com%2Fuploads%2Farticles%2Fdi2nn48p3s3yavskgyit.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.amazonaws.com%2Fuploads%2Farticles%2Fdi2nn48p3s3yavskgyit.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 8: Trigger the Pipeline
&lt;/h3&gt;

&lt;p&gt;Now go back to your&amp;nbsp;&lt;code&gt;cicd-copilot&lt;/code&gt;&amp;nbsp;project and click&amp;nbsp;&lt;strong&gt;Build Now&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Open&amp;nbsp;&lt;strong&gt;Console Output&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You will notice that the pipeline fails — this is intentional.&lt;/p&gt;

&lt;p&gt;The logs are automatically captured and sent to the AI Agent, which returns structured debugging analysis inside the Jenkins console.&lt;/p&gt;

&lt;p&gt;In the first failure, the AI identifies a typo in the&amp;nbsp;&lt;code&gt;Dockerfile&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
For example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alpine
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2F7x8kfvio401a3e7js9f9.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.amazonaws.com%2Fuploads%2Farticles%2F7x8kfvio401a3e7js9f9.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fix the typo in your forked repository and commit the changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 9: Second Failure (Version Mismatch)
&lt;/h3&gt;

&lt;p&gt;Rebuild the pipeline.&lt;/p&gt;

&lt;p&gt;This time, the pipeline fails again — but for a different reason. There is a Docker image version mismatch.&lt;/p&gt;

&lt;p&gt;The AI analysis might suggest that the image is private or unavailable. However, the real issue is in the&amp;nbsp;&lt;code&gt;Jenkinsfile&lt;/code&gt;.&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.amazonaws.com%2Fuploads%2Farticles%2Febqz4we61kdwcryd23im.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.amazonaws.com%2Fuploads%2Farticles%2Febqz4we61kdwcryd23im.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Inside the&amp;nbsp;&lt;strong&gt;Run Container&lt;/strong&gt;&amp;nbsp;stage, change the image version from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2Fvk58wuca063hl439ayyc.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.amazonaws.com%2Fuploads%2Farticles%2Fvk58wuca063hl439ayyc.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Commit the change and rebuild the pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 10: Successful Pipeline Run
&lt;/h3&gt;

&lt;p&gt;Now, when you trigger the pipeline again:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The build succeeds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Docker image is pushed to your Docker Hub account&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The container starts successfully&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Visit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://localhost:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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.amazonaws.com%2Fuploads%2Farticles%2F2pv69etz5eff4fq4n07m.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.amazonaws.com%2Fuploads%2Farticles%2F2pv69etz5eff4fq4n07m.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You should see the Book Reader application running.&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.amazonaws.com%2Fuploads%2Farticles%2Fj02s0pswmjum51a49154.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.amazonaws.com%2Fuploads%2Farticles%2Fj02s0pswmjum51a49154.png" alt=" "&gt;&lt;/a&gt;&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.amazonaws.com%2Fuploads%2Farticles%2F5fj73w6v0folxas2x8vs.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.amazonaws.com%2Fuploads%2Farticles%2F5fj73w6v0folxas2x8vs.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Stop the Application
&lt;/h3&gt;

&lt;p&gt;To stop the running container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;kill &lt;/span&gt;cicd-copilot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Clean Up Infrastructure
&lt;/h3&gt;

&lt;p&gt;To avoid unnecessary AWS charges, destroy the infrastructure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy &lt;span class="nt"&gt;-var&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"gemini_api_key=&amp;lt;Paste-your-key-here&amp;gt;"&lt;/span&gt; &lt;span class="nt"&gt;--auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What We Achieved
&lt;/h3&gt;

&lt;p&gt;In this project, we built an AI-powered CI/CD Copilot using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Jenkins for pipeline orchestration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS Lambda for AI agent logic&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AWS Secrets Manager for secure API storage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google Gemini API for log analysis&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The agent receives contextual pipeline information and failure logs, analyzes them intelligently, and provides structured debugging insights directly inside the CI/CD workflow.&lt;/p&gt;

&lt;p&gt;Instead of manually scanning logs, you now have an AI assistant that understands context, categorizes failures, identifies root causes, and suggests actionable fixes — making debugging faster, smarter, and more efficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Conclusion
&lt;/h2&gt;

&lt;p&gt;Modern CI/CD pipelines are powerful — but when they fail, debugging can quickly become time-consuming and frustrating. In this project, we went a step further by integrating AI directly into the pipeline workflow.&lt;/p&gt;

&lt;p&gt;By combining:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Jenkins&lt;/strong&gt;&amp;nbsp;for orchestration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Lambda&lt;/strong&gt;&amp;nbsp;for serverless execution&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AWS Secrets Manager&lt;/strong&gt;&amp;nbsp;for secure API handling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Google Gemini API&lt;/strong&gt;&amp;nbsp;for intelligent log analysis&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;we built an AI-powered CI/CD Copilot capable of understanding pipeline context, analyzing failure logs, identifying root causes, and suggesting actionable fixes — all automatically.&lt;/p&gt;

&lt;p&gt;This isn’t just about log analysis. It’s about shifting from reactive debugging to intelligent, context-aware automation.&lt;/p&gt;

&lt;p&gt;As AI continues to evolve, integrating agentic systems into DevOps workflows will become increasingly common. Building projects like this not only strengthens your cloud and automation skills but also prepares you for the next wave of AI-driven infrastructure.&lt;/p&gt;

&lt;p&gt;If you found this project helpful, feel free to connect with me and follow my work:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;🌐&amp;nbsp;&lt;strong&gt;Website:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://praveshsudha.com" rel="noopener noreferrer"&gt;https://praveshsudha.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;📝&amp;nbsp;&lt;strong&gt;Blog:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://blog.praveshsudha.com" rel="noopener noreferrer"&gt;https://blog.praveshsudha.com&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;💼&amp;nbsp;&lt;strong&gt;LinkedIn:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://www.linkedin.com/in/pravesh-sudha" rel="noopener noreferrer"&gt;https://www.linkedin.com/in/pravesh-sudha&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🐙&amp;nbsp;&lt;strong&gt;GitHub:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://github.com/Pravesh-Sudha" rel="noopener noreferrer"&gt;https://github.com/Pravesh-Sudha&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;🐦&amp;nbsp;&lt;strong&gt;Twitter/X:&lt;/strong&gt;&amp;nbsp;&lt;a href="https://x.com/praveshstwt" rel="noopener noreferrer"&gt;https://x.com/praveshstwt&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;🎥 Youtube&lt;/strong&gt;: &lt;a href="https://youtube.com/@pravesh-sudha" rel="noopener noreferrer"&gt;https://youtube.com/@pravesh-sudha&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I regularly share content on DevOps, AWS, Terraform, CI/CD, and building real-world cloud projects from scratch.&lt;/p&gt;

&lt;p&gt;If you build your own version of this AI CI/CD Copilot, tag me — I’d love to see what you create.&lt;/p&gt;

&lt;p&gt;Happy Building 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>jenkins</category>
      <category>cicd</category>
    </item>
  </channel>
</rss>
