<?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>Forem: Hamdi (KHELIL) LION</title>
    <description>The latest articles on Forem by Hamdi (KHELIL) LION (@hkhelil).</description>
    <link>https://forem.com/hkhelil</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1930768%2Fa35af795-ee57-49bc-aa3a-3dd7906953e3.jpeg</url>
      <title>Forem: Hamdi (KHELIL) LION</title>
      <link>https://forem.com/hkhelil</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/hkhelil"/>
    <language>en</language>
    <item>
      <title>🧠 Demystifying Metrics in Kubernetes</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Mon, 09 Feb 2026 07:48:25 +0000</pubDate>
      <link>https://forem.com/hkhelil/demystifying-metrics-in-kubernetes-id7</link>
      <guid>https://forem.com/hkhelil/demystifying-metrics-in-kubernetes-id7</guid>
      <description>&lt;p&gt;Kubernetes does not magically know when to scale your workloads 🤖&lt;br&gt;
It relies on &lt;strong&gt;metrics exposed through dedicated APIs&lt;/strong&gt; to make scaling decisions.&lt;/p&gt;

&lt;p&gt;There are &lt;strong&gt;three types of metrics&lt;/strong&gt; you need to understand:&lt;/p&gt;

&lt;p&gt;⚙️ Resource Metrics&lt;br&gt;
📊 Custom Metrics&lt;br&gt;
🌍 External Metrics&lt;/p&gt;

&lt;p&gt;Each one represents a different kind of pressure on your system.&lt;/p&gt;
&lt;h2&gt;
  
  
  🤔 Why Metrics Matter for Autoscaling
&lt;/h2&gt;

&lt;p&gt;Autoscaling in Kubernetes is mainly handled by the &lt;strong&gt;Horizontal Pod Autoscaler&lt;/strong&gt; (HPA).&lt;/p&gt;

&lt;p&gt;The HPA keeps asking:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Hey… are my Pods struggling?” 😅&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The answer comes from metrics. Without them, Kubernetes is basically guessing.&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚙️ 1 Resource Metrics = Pod Health Signals
&lt;/h2&gt;

&lt;p&gt;These are the &lt;strong&gt;native Kubernetes metrics&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They come from the &lt;strong&gt;Metrics Server&lt;/strong&gt; and only cover:&lt;/p&gt;

&lt;p&gt;🧮 CPU usage&lt;br&gt;
🧠 Memory usage&lt;/p&gt;

&lt;p&gt;They are exposed via:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;metrics.k8s.io&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🧩 What they represent
&lt;/h3&gt;

&lt;p&gt;They describe &lt;strong&gt;resource consumption&lt;/strong&gt;, not business traffic.&lt;/p&gt;

&lt;p&gt;Your app might be slow because of a database… but CPU could still be chill 🧊&lt;/p&gt;
&lt;h3&gt;
  
  
  📄 Example HPA based on CPU
&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;autoscaling/v2&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;HorizontalPodAutoscaler&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;api-hpa&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;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
  &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&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;Resource&lt;/span&gt;
    &lt;span class="na"&gt;resource&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;cpu&lt;/span&gt;
      &lt;span class="na"&gt;target&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;Utilization&lt;/span&gt;
        &lt;span class="na"&gt;averageUtilization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;If average CPU across Pods goes above 70 percent → more replicas 🔥&lt;/p&gt;
&lt;h3&gt;
  
  
  ✅ Pros
&lt;/h3&gt;

&lt;p&gt;Super simple&lt;br&gt;
Works out of the box&lt;/p&gt;
&lt;h3&gt;
  
  
  ❌ Limits
&lt;/h3&gt;

&lt;p&gt;CPU is not always equal to user traffic&lt;br&gt;
Memory often reacts too late&lt;/p&gt;
&lt;h2&gt;
  
  
  📊 2 Custom Metrics = Your App Talking to Kubernetes
&lt;/h2&gt;

&lt;p&gt;Custom metrics come from &lt;strong&gt;applications inside your cluster&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They describe &lt;strong&gt;business or application load&lt;/strong&gt; 💼&lt;/p&gt;

&lt;p&gt;They are exposed through:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;custom.metrics.k8s.io&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  🔁 Typical data flow
&lt;/h3&gt;

&lt;p&gt;App exposes &lt;code&gt;/metrics&lt;/code&gt;&lt;br&gt;
Prometheus scrapes&lt;br&gt;
Prometheus Adapter maps metrics → Kubernetes API&lt;/p&gt;

&lt;p&gt;Now Kubernetes can ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“How busy is this Deployment really?” 👀&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  🌐 Example 1 HTTP requests per second
&lt;/h3&gt;

&lt;p&gt;Metric in Prometheus:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;http_requests_per_second&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧠 What it means&lt;br&gt;
Real traffic handled by each Pod&lt;/p&gt;

&lt;p&gt;📦 In Kubernetes&lt;br&gt;
A metric attached to Pods&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;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&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;Pods&lt;/span&gt;
  &lt;span class="na"&gt;pods&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metric&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;http_requests_per_second&lt;/span&gt;
    &lt;span class="na"&gt;target&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;AverageValue&lt;/span&gt;
      &lt;span class="na"&gt;averageValue&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If each Pod handles more than 200 requests per second → scale out 🚀&lt;/p&gt;

&lt;h3&gt;
  
  
  ⏱ Example 2 Request duration
&lt;/h3&gt;

&lt;p&gt;Metric:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;request_duration_seconds&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧠 What it means&lt;br&gt;
Application performance and saturation&lt;/p&gt;

&lt;p&gt;Used as an Object metric:&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;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Object&lt;/span&gt;
  &lt;span class="na"&gt;object&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metric&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;avg_request_duration&lt;/span&gt;
    &lt;span class="na"&gt;describedObject&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api&lt;/span&gt;
    &lt;span class="na"&gt;target&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;Value&lt;/span&gt;
      &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If average latency goes above 500ms → time to add Pods 🏃‍♂️&lt;/p&gt;

&lt;h3&gt;
  
  
  🧵 Example 3 Active background jobs
&lt;/h3&gt;

&lt;p&gt;Metric:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;active_background_jobs&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧠 What it means&lt;br&gt;
Internal workload of a worker&lt;/p&gt;

&lt;p&gt;Each Pod reports its own load, and HPA scales when workers are overloaded 📈&lt;/p&gt;
&lt;h3&gt;
  
  
  ✅ Pros
&lt;/h3&gt;

&lt;p&gt;Scaling reflects real app behavior&lt;br&gt;
Way smarter than CPU only&lt;/p&gt;
&lt;h3&gt;
  
  
  ❌ Limits
&lt;/h3&gt;

&lt;p&gt;Requires Prometheus + Prometheus Adapter&lt;br&gt;
More components to maintain 😬&lt;/p&gt;
&lt;h2&gt;
  
  
  🌍 3 External Metrics = Work Waiting Outside the Cluster
&lt;/h2&gt;

&lt;p&gt;External metrics come from &lt;strong&gt;systems outside Kubernetes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;They are exposed via:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;external.metrics.k8s.io&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;These metrics describe work your Pods must process, even if it lives elsewhere 🌎&lt;/p&gt;
&lt;h3&gt;
  
  
  📬 Example 1 SQS queue length
&lt;/h3&gt;

&lt;p&gt;Metric:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ApproximateNumberOfMessagesVisible&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧠 What it means&lt;br&gt;
Number of messages waiting in the queue&lt;/p&gt;

&lt;p&gt;In Kubernetes&lt;br&gt;
A global metric not tied to specific Pods&lt;/p&gt;

&lt;p&gt;If the queue grows → spawn more workers 💪&lt;/p&gt;
&lt;h3&gt;
  
  
  🐘 Example 2 Kafka consumer lag
&lt;/h3&gt;

&lt;p&gt;Metric:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kafka_consumer_lag&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧠 What it means&lt;br&gt;
Delay between producers and consumers&lt;/p&gt;

&lt;p&gt;More lag = your consumers are falling behind 😱&lt;br&gt;
Scale them up!&lt;/p&gt;
&lt;h3&gt;
  
  
  📦 Example 3 Redis job queue size
&lt;/h3&gt;

&lt;p&gt;Metric:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;redis_list_length&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;🧠 What it means&lt;br&gt;
Number of jobs waiting in Redis queues&lt;/p&gt;

&lt;p&gt;Perfect for worker autoscaling 🔄&lt;/p&gt;
&lt;h3&gt;
  
  
  ⏰ Example 4 Time based scaling
&lt;/h3&gt;

&lt;p&gt;Scale more Pods during office hours&lt;br&gt;
Scale down at night 🌙&lt;/p&gt;

&lt;p&gt;This is also treated as an external signal, because it’s not tied to Pod resource usage.&lt;/p&gt;
&lt;h2&gt;
  
  
  🧮 How HPA Uses These Metrics
&lt;/h2&gt;

&lt;p&gt;HPA periodically queries:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric Type&lt;/th&gt;
&lt;th&gt;API&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Resource&lt;/td&gt;
&lt;td&gt;metrics.k8s.io&lt;/td&gt;
&lt;td&gt;CPU and memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom&lt;/td&gt;
&lt;td&gt;custom.metrics.k8s.io&lt;/td&gt;
&lt;td&gt;App level load&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;External&lt;/td&gt;
&lt;td&gt;external.metrics.k8s.io&lt;/td&gt;
&lt;td&gt;Event or system load&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Then it calculates how many replicas you need 📐&lt;/p&gt;
&lt;h2&gt;
  
  
  ⚡ KEDA vs Prometheus Adapter
&lt;/h2&gt;

&lt;p&gt;Here comes the game changer 🎮&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;KEDA&lt;/strong&gt; (Kubernetes Event Driven Autoscaling) focuses on &lt;strong&gt;event driven autoscaling&lt;/strong&gt; and makes custom and external metrics way easier to use.&lt;/p&gt;
&lt;h3&gt;
  
  
  🧩 With Prometheus Adapter
&lt;/h3&gt;

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

&lt;p&gt;Run Prometheus&lt;br&gt;
Install and configure the Adapter&lt;br&gt;
Write mapping rules&lt;br&gt;
Manage RBAC and certs&lt;/p&gt;

&lt;p&gt;It works, but it’s heavy 🏋️&lt;/p&gt;
&lt;h3&gt;
  
  
  ⚡ With KEDA
&lt;/h3&gt;

&lt;p&gt;You define a &lt;strong&gt;ScaledObject&lt;/strong&gt; and KEDA does the magic ✨&lt;/p&gt;

&lt;p&gt;Example SQS scaler:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;keda.sh/v1alpha1&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;ScaledObject&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;worker-scaler&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;scaleTargetRef&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;worker&lt;/span&gt;
  &lt;span class="na"&gt;minReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicaCount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt;
  &lt;span class="na"&gt;triggers&lt;/span&gt;&lt;span class="pi"&gt;:&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;aws-sqs-queue&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;queueURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://sqs.eu-west-1.amazonaws.com/123/my-queue&lt;/span&gt;
      &lt;span class="na"&gt;queueLength&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;10"&lt;/span&gt;
      &lt;span class="na"&gt;awsRegion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;eu-west-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;KEDA fetches the metric&lt;br&gt;
Exposes it to Kubernetes&lt;br&gt;
Creates and manages the HPA&lt;br&gt;
Handles provider auth 🔐&lt;/p&gt;

&lt;p&gt;All without Prometheus Adapter 🤯&lt;/p&gt;

&lt;h2&gt;
  
  
  🧭 When to Use What
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you want to scale on…&lt;/th&gt;
&lt;th&gt;Use…&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CPU or memory&lt;/td&gt;
&lt;td&gt;Resource metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP traffic or app load&lt;/td&gt;
&lt;td&gt;Custom metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Queues, streams, SaaS&lt;/td&gt;
&lt;td&gt;External metrics + KEDA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Events or scale to zero&lt;/td&gt;
&lt;td&gt;KEDA&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  🎯 Final Takeaway
&lt;/h2&gt;

&lt;p&gt;Kubernetes becomes truly powerful when scaling is driven by &lt;strong&gt;real workload signals&lt;/strong&gt;, not just CPU.&lt;/p&gt;

&lt;p&gt;⚙️ Resource metrics are the starting point&lt;br&gt;
📊 Custom metrics bring application awareness&lt;br&gt;
🌍 External metrics unlock event driven architectures&lt;br&gt;
⚡ KEDA makes advanced autoscaling simple and production friendly&lt;/p&gt;

&lt;p&gt;Once you understand these three metric types, autoscaling stops being magic and becomes a design tool you control 💡🚀&lt;/p&gt;

&lt;p&gt;Happy clustering :)&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cloud</category>
      <category>keda</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building a Kubernetes homelab the hard but right way 🧱☸️</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Tue, 20 Jan 2026 10:32:46 +0000</pubDate>
      <link>https://forem.com/hkhelil/building-a-kubernetes-homelab-the-hard-but-right-way-308k</link>
      <guid>https://forem.com/hkhelil/building-a-kubernetes-homelab-the-hard-but-right-way-308k</guid>
      <description>&lt;p&gt;If you have been following my previous articles, you already know the drill 😄&lt;br&gt;
I like setups that are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;boring&lt;/li&gt;
&lt;li&gt;repeatable&lt;/li&gt;
&lt;li&gt;close to real production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This article is not about spinning up a quick cluster.&lt;br&gt;
It is about &lt;strong&gt;building a platform&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A platform you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rebuild from scratch&lt;/li&gt;
&lt;li&gt;scale without fear&lt;/li&gt;
&lt;li&gt;explain to a client or a teammate&lt;/li&gt;
&lt;li&gt;reuse in real consulting missions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And yes, it runs at home 👀&lt;/p&gt;

&lt;h2&gt;
  
  
  The mindset first 🧠
&lt;/h2&gt;

&lt;p&gt;Before touching any tool, I decided on one rule:&lt;/p&gt;

&lt;p&gt;👉 no manual action that I cannot reproduce with code&lt;/p&gt;

&lt;p&gt;That single rule drives everything else.&lt;/p&gt;

&lt;p&gt;So the stack becomes very natural:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proxmox for virtualization&lt;/li&gt;
&lt;li&gt;Terraform to create machines&lt;/li&gt;
&lt;li&gt;Cloud Init to bootstrap the OS&lt;/li&gt;
&lt;li&gt;Kubespray to install Kubernetes&lt;/li&gt;
&lt;li&gt;Helmfile for everything after day 1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each layer has &lt;strong&gt;one job only&lt;/strong&gt;.&lt;br&gt;
No overlap. No shortcuts.&lt;/p&gt;

&lt;h2&gt;
  
  
  The big picture 🗺️
&lt;/h2&gt;

&lt;p&gt;Here is what actually happens when I type terraform apply 👇&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Proxmox clones VMs from a cloud init template&lt;/li&gt;
&lt;li&gt;Each VM boots with the right user, SSH keys and network&lt;/li&gt;
&lt;li&gt;Nodes are reachable immediately&lt;/li&gt;
&lt;li&gt;Kubespray turns them into a real HA Kubernetes cluster&lt;/li&gt;
&lt;li&gt;Helmfile deploys platform components on top&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Once you understand that flow, debugging becomes easy and scaling becomes boring.&lt;br&gt;
And boring is good 😌&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1 Proxmox as a real compute layer ⚙️
&lt;/h2&gt;

&lt;p&gt;I treat Proxmox like a private cloud, not like a lab UI.&lt;/p&gt;

&lt;p&gt;No clicking. No guessing.&lt;/p&gt;

&lt;p&gt;Terraform talks directly to the Proxmox API and creates Kubernetes nodes exactly the same way every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I am doing here
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;cloning from a golden cloud init template&lt;/li&gt;
&lt;li&gt;injecting user data via snippets&lt;/li&gt;
&lt;li&gt;assigning static IPs&lt;/li&gt;
&lt;li&gt;tagging nodes for clarity&lt;/li&gt;
&lt;li&gt;keeping compute concerns separate from Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this stage, Kubernetes does not exist yet.&lt;br&gt;
And that is exactly what I want 👍&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2 The cluster is just data 📐
&lt;/h2&gt;

&lt;p&gt;This is one of my favorite parts.&lt;/p&gt;

&lt;p&gt;The cluster topology lives in a tfvars file.&lt;br&gt;
No logic. No magic. Just data.&lt;/p&gt;

&lt;p&gt;This is extremely important because it means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the same code can create one cluster or ten&lt;/li&gt;
&lt;li&gt;topology changes do not require refactoring&lt;/li&gt;
&lt;li&gt;environments stay consistent over time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adding a node or a whole new cluster is just editing data 🔁&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3 Reusable Terraform modules 🧩
&lt;/h2&gt;

&lt;p&gt;Everything goes through a compute module.&lt;/p&gt;

&lt;p&gt;Terraform is responsible for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;machines&lt;/li&gt;
&lt;li&gt;networking&lt;/li&gt;
&lt;li&gt;bootstrapping access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And nothing else.&lt;/p&gt;

&lt;p&gt;Once the VMs exist, Terraform is basically done.&lt;/p&gt;

&lt;p&gt;This clear boundary avoids a lot of confusion later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 4 Kubespray does the heavy lifting ☸️
&lt;/h2&gt;

&lt;p&gt;Kubespray is where Kubernetes actually comes to life.&lt;/p&gt;

&lt;p&gt;It handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HA control plane&lt;/li&gt;
&lt;li&gt;stacked etcd&lt;/li&gt;
&lt;li&gt;container runtime&lt;/li&gt;
&lt;li&gt;CNI and kubelet configuration&lt;/li&gt;
&lt;li&gt;sane defaults and hardening&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is not a quick kubeadm script.&lt;br&gt;
This is a production grade installer that I fully trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 5 Bootstrap only the essentials 🚦
&lt;/h2&gt;

&lt;p&gt;At cluster creation time, I only enable what is strictly required:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;metrics server&lt;/li&gt;
&lt;li&gt;ingress nginx&lt;/li&gt;
&lt;li&gt;metallb&lt;/li&gt;
&lt;li&gt;gateway api&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Nothing fancy. Nothing opinionated.&lt;/p&gt;

&lt;p&gt;The goal here is to have a &lt;strong&gt;usable Kubernetes cluster&lt;/strong&gt;, not a fully loaded platform.&lt;/p&gt;

&lt;p&gt;Everything else can wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 6 Day 2 is simple and explicit with Helmfile 📦
&lt;/h2&gt;

&lt;p&gt;This is important.&lt;/p&gt;

&lt;p&gt;There is &lt;strong&gt;no fancy GitOps setup here&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;No Argo CD bootstrap.&lt;br&gt;
No Flux managing Flux.&lt;br&gt;
No recursive GitOps inception 😄&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Helmfile is run explicitly&lt;/li&gt;
&lt;li&gt;changes are intentional&lt;/li&gt;
&lt;li&gt;failures are visible&lt;/li&gt;
&lt;li&gt;debugging stays simple&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a single cluster or a homelab, this is often the best tradeoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  When GitOps really makes sense 🚀
&lt;/h2&gt;

&lt;p&gt;Now, things change when you have &lt;strong&gt;multiple clusters to manage&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;At that point, this setup becomes extremely powerful.&lt;/p&gt;

&lt;p&gt;Because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform already creates the machines&lt;/li&gt;
&lt;li&gt;Kubespray already installs Kubernetes&lt;/li&gt;
&lt;li&gt;Helmfile already describes the platform state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can easily plug a GitOps layer on top to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create a new cluster from scratch&lt;/li&gt;
&lt;li&gt;apply a standard baseline&lt;/li&gt;
&lt;li&gt;end up with a ready to use Kubernetes platform&lt;/li&gt;
&lt;li&gt;with almost zero manual action&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as:&lt;br&gt;
👉 one command to pop a full Kubernetes cluster, ready for workloads&lt;/p&gt;

&lt;p&gt;This is where Argo CD or Flux start to shine, but &lt;strong&gt;only when the scale justifies it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Until then, keeping things simple is often the most mature choice 🧘&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I like this approach so much ❤️
&lt;/h2&gt;

&lt;p&gt;Because it grows with you.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simple when you have one cluster&lt;/li&gt;
&lt;li&gt;scalable when you have many&lt;/li&gt;
&lt;li&gt;understandable at every step&lt;/li&gt;
&lt;li&gt;close to how real platforms are built&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No magic.&lt;br&gt;
No hidden automation.&lt;br&gt;
Just solid engineering.&lt;/p&gt;

&lt;p&gt;If you can run this at home, you can run it anywhere 🌍&lt;/p&gt;

&lt;h2&gt;
  
  
  What is next 🔮
&lt;/h2&gt;

&lt;p&gt;In upcoming articles I will dive into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;storage choices and csi&lt;/li&gt;
&lt;li&gt;dns and external-dns&lt;/li&gt;
&lt;li&gt;security with kyverno&lt;/li&gt;
&lt;li&gt;observability and metrics&lt;/li&gt;
&lt;li&gt;multi cluster patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned 👋 and happy clustering :) &lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cloud</category>
      <category>devops</category>
      <category>containers</category>
    </item>
    <item>
      <title>Building a Kubernetes HomeLab - The Hard Way - DNS first, or everything else lies to you 🔥🌐</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Mon, 22 Dec 2025 08:08:54 +0000</pubDate>
      <link>https://forem.com/hkhelil/building-a-kubernetes-homelab-the-hard-way-dns-first-or-everything-else-lies-to-you-3ng1</link>
      <guid>https://forem.com/hkhelil/building-a-kubernetes-homelab-the-hard-way-dns-first-or-everything-else-lies-to-you-3ng1</guid>
      <description>&lt;p&gt;Let’s be very clear from the start 👇&lt;br&gt;
If DNS is shaky, everything above it becomes unreliable, misleading, and borderline hostile.&lt;/p&gt;

&lt;p&gt;You will blame Kubernetes.&lt;br&gt;
You will blame cert-manager.&lt;br&gt;
You will blame yourself.&lt;/p&gt;

&lt;p&gt;And it will still be DNS 😅&lt;/p&gt;

&lt;p&gt;This article builds a real DNS layer, outside the cluster, the way it is done in production environments.&lt;br&gt;
Not fancy. Not clever. Just solid.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;DNS is not a feature, it is the foundation 🧱&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most homelabs treat DNS like a checkbox:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;router default&lt;/li&gt;
&lt;li&gt;maybe 1.1.1.1&lt;/li&gt;
&lt;li&gt;maybe 8.8.8.8&lt;/li&gt;
&lt;li&gt;call it a day&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works until you introduce:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;multiple Proxmox nodes&lt;/li&gt;
&lt;li&gt;Kubernetes bootstrap&lt;/li&gt;
&lt;li&gt;internal TLS&lt;/li&gt;
&lt;li&gt;ExternalDNS&lt;/li&gt;
&lt;li&gt;service to service communication
At that point, DNS stops being optional.
It becomes infrastructure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this layer lies, everything else lies with it.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;what I assume before we start breaking things 🔧&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This article assumes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;you are comfortable with Linux and SSH&lt;/li&gt;
&lt;li&gt;you control your LAN&lt;/li&gt;
&lt;li&gt;you can assign static IPs&lt;/li&gt;
&lt;li&gt;you are fine rebuilding things if needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Network baseline used in examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LAN CIDR: 192.168.1.0/24&lt;/li&gt;
&lt;li&gt;router: 192.168.1.1&lt;/li&gt;
&lt;li&gt;DNS node: 192.168.1.169&lt;/li&gt;
&lt;li&gt;internal domain: home.arpa&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why home.arpa?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;defined by RFC 8375&lt;/li&gt;
&lt;li&gt;designed for private networks&lt;/li&gt;
&lt;li&gt;no collision with public DNS&lt;/li&gt;
&lt;li&gt;predictable behavior across operating systems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;.local is convenient.&lt;br&gt;
.local is also a trap ☠️&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;how DNS flows in this homelab, no magic involved 🧭&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The DNS request path is intentionally boring:&lt;/p&gt;

&lt;p&gt;client&lt;br&gt;
→ Pi-hole&lt;br&gt;
→ Unbound&lt;br&gt;
→ root servers&lt;br&gt;
→ TLD servers&lt;br&gt;
→ authoritative servers&lt;/p&gt;

&lt;p&gt;Why this design?&lt;/p&gt;

&lt;p&gt;Pi-hole sits in front:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;single DNS endpoint for the entire LAN&lt;/li&gt;
&lt;li&gt;full visibility into queries&lt;/li&gt;
&lt;li&gt;local DNS records for infrastructure&lt;/li&gt;
&lt;li&gt;API compatible with ExternalDNS later&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Unbound sits behind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;full recursive resolver&lt;/li&gt;
&lt;li&gt;DNSSEC validation&lt;/li&gt;
&lt;li&gt;zero dependency on Google or Cloudflare&lt;/li&gt;
&lt;li&gt;behaves like enterprise DNS stacks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One DNS server for the LAN:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;deterministic behavior&lt;/li&gt;
&lt;li&gt;easier troubleshooting&lt;/li&gt;
&lt;li&gt;required for Kubernetes stability&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;building the DNS node, properly and calmly 🧱
A Raspberry Pi that does exactly one thing 🥧&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Reserve a static IP from your router:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;hostname: dns-01&lt;/li&gt;
&lt;li&gt;ip: 192.168.1.169&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Update the system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt update
sudo apt upgrade -y
sudo apt install curl gnupg lsb-release -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This node is sacred:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;DNS only&lt;/li&gt;
&lt;li&gt;no Docker&lt;/li&gt;
&lt;li&gt;no Kubernetes&lt;/li&gt;
&lt;li&gt;no experiments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If DNS breaks, you want zero doubt about the cause.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;installing Pi-hole, the DNS entry point 🚦&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pi-hole will be the only DNS server exposed to the LAN.&lt;/p&gt;

&lt;p&gt;Install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sSL https://install.pi-hole.net | bash

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During the installer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;interface: eth0&lt;/li&gt;
&lt;li&gt;upstream DNS: custom&lt;/li&gt;
&lt;li&gt;do not select any public DNS provider&lt;/li&gt;
&lt;li&gt;enable the web admin interface&lt;/li&gt;
&lt;/ul&gt;

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

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

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Immediately change the admin password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pihole -a -p
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this stage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pi-hole is answering DNS queries&lt;/li&gt;
&lt;li&gt;but it should not resolve anything yet&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;installing Unbound, the actual DNS resolver 🔁&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unbound will do the real work: recursive resolution and DNSSEC validation.&lt;/p&gt;

&lt;p&gt;Install Unbound:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo apt install unbound -y
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a dedicated configuration for Pi-hole:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sudo tee /etc/unbound/unbound.conf.d/pi-hole.conf &amp;gt; /dev/null &amp;lt;&amp;lt;EOF
server:
  interface: 127.0.0.1
  port: 5335
  do-ip4: yes
  do-ip6: no
  do-udp: yes
  do-tcp: yes
  root-hints: "/var/lib/unbound/root.hints"
  harden-glue: yes
  harden-dnssec-stripped: yes
  use-caps-for-id: yes
  edns-buffer-size: 1232
  prefetch: yes
  verbosity: 1

forward-zone:
  name: "."
  forward-addr: 0.0.0.0
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fetch root hints:&lt;/p&gt;

&lt;p&gt;sudo curl -o /var/lib/unbound/root.hints &lt;a href="https://www.internic.net/domain/named.root" rel="noopener noreferrer"&gt;https://www.internic.net/domain/named.root&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Enable and start Unbound:&lt;/p&gt;

&lt;p&gt;sudo systemctl restart unbound&lt;br&gt;
sudo systemctl enable unbound&lt;/p&gt;

&lt;p&gt;At this point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Unbound listens on 127.0.0.1:5335&lt;/li&gt;
&lt;li&gt;performs full recursive resolution&lt;/li&gt;
&lt;li&gt;validates DNSSEC&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;connecting Pi-hole to Unbound, the clean way 🔗&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the Pi-hole admin UI:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;settings&lt;/li&gt;
&lt;li&gt;dns&lt;/li&gt;
&lt;li&gt;upstream DNS servers&lt;/li&gt;
&lt;li&gt;custom server: 127.0.0.1#5335&lt;/li&gt;
&lt;li&gt;disable all other upstream resolvers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now the chain is complete:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;clients talk to Pi-hole&lt;/li&gt;
&lt;li&gt;Pi-hole forwards to Unbound&lt;/li&gt;
&lt;li&gt;Unbound talks to the internet&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No leaks.&lt;br&gt;
No shortcuts.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;naming things like an adult 🏷️&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;internal DNS records&lt;/p&gt;

&lt;p&gt;Define local DNS records in Pi-hole:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;proxmox-01.home.arpa   192.168.1.41
proxmox-02.home.arpa   192.168.1.42
proxmox-03.home.arpa   192.168.1.43
nas-01.home.arpa       192.168.1.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ip plan&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;component   | ip
router          | 192.168.1.1
dns-01          | 192.168.1.169
proxmox-01  | 192.168.1.41
proxmox-02  | 192.168.1.42
proxmox-03  | 192.168.1.43
synology    | 192.168.1.50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Predictable naming beats clever naming.&lt;br&gt;
Every single time.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;proof before trust, always 🔍&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Test recursive resolution:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dig google.com @192.168.1.169

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test DNSSEC:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dig dnssec-failed.org @192.168.1.169

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;SERVFAIL means DNSSEC is working ✅
Test internal names:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dig proxmox-01.home.arpa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch live DNS traffic:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;If something breaks later, this is where you start.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;how people accidentally sabotage their own DNS ☠️&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Adding a public DNS as secondary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;internal names stop resolving&lt;/li&gt;
&lt;li&gt;ExternalDNS becomes unreliable&lt;/li&gt;
&lt;li&gt;cert-manager fails silently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using .local:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mDNS conflicts&lt;/li&gt;
&lt;li&gt;inconsistent resolution&lt;/li&gt;
&lt;li&gt;debugging hell&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Running DNS inside Kubernetes first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;bootstrap deadlock&lt;/li&gt;
&lt;li&gt;cluster depends on itself&lt;/li&gt;
&lt;li&gt;guaranteed pain&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;why this DNS layer unlocks everything else 🚀&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With this setup in place:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proxmox nodes resolve consistently&lt;/li&gt;
&lt;li&gt;Kubernetes nodes bootstrap cleanly&lt;/li&gt;
&lt;li&gt;ExternalDNS can manage records safely&lt;/li&gt;
&lt;li&gt;cert-manager can issue internal certificates&lt;/li&gt;
&lt;li&gt;ingress becomes predictable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the point where the homelab stops feeling fragile.&lt;/p&gt;

&lt;p&gt;Next article:&lt;br&gt;
Teaching your router who’s boss 🧠📡&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;lessons learned the hard way, so you do not have to 😅&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;DNS should be boring.&lt;br&gt;
Silent.&lt;br&gt;
Predictable.&lt;/p&gt;

&lt;p&gt;If DNS is exciting, something is wrong.&lt;/p&gt;

&lt;p&gt;This setup is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;reproducible&lt;/li&gt;
&lt;li&gt;observable&lt;/li&gt;
&lt;li&gt;production-inspired&lt;/li&gt;
&lt;li&gt;intentionally boring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hard now.&lt;br&gt;
Peace later.&lt;/p&gt;

&lt;p&gt;Happy Clustering :) &lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>homelab</category>
      <category>cloud</category>
      <category>devops</category>
    </item>
    <item>
      <title>🏗️ Setting Up a Proxmox Cluster for My Homelab Kubernetes Environment</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Wed, 05 Nov 2025 06:27:25 +0000</pubDate>
      <link>https://forem.com/hkhelil/setting-up-a-proxmox-cluster-for-my-homelab-kubernetes-environment-1jop</link>
      <guid>https://forem.com/hkhelil/setting-up-a-proxmox-cluster-for-my-homelab-kubernetes-environment-1jop</guid>
      <description>&lt;p&gt;Hey everyone 👋&lt;/p&gt;

&lt;p&gt;Now that you know the hardware powering my homelab&lt;br&gt;
it is time to show how I turned those machines into a Proxmox cluster ready to host a production style Kubernetes environment 🧑‍💻⚙️&lt;/p&gt;

&lt;p&gt;This article covers the entire setup&lt;br&gt;
including fixes for a few annoying real world problems 😅&lt;/p&gt;

&lt;p&gt;Let’s get into it 🚀&lt;/p&gt;

&lt;p&gt;✅ Step 1. Update BIOS on Lenovo Tiny Nodes&lt;/p&gt;

&lt;p&gt;Before installing Proxmox&lt;br&gt;
I strongly recommend updating the BIOS of each node&lt;/p&gt;

&lt;p&gt;Why it matters&lt;br&gt;
✅ fixes stability bugs&lt;br&gt;
✅ improves CPU performance and thermal handling&lt;br&gt;
✅ avoids random shutdowns or throttling&lt;br&gt;
✅ unlocks better hardware support&lt;/p&gt;

&lt;p&gt;Old BIOS = unpredictable issues later&lt;br&gt;
Flash now and enjoy peace later ✅&lt;/p&gt;

&lt;p&gt;✅ Step 2. Install Proxmox VE&lt;/p&gt;

&lt;p&gt;I installed Proxmox VE on each of my three Lenovo M920q nodes&lt;br&gt;
using this simple naming and addressing plan:&lt;/p&gt;

&lt;p&gt;Node    Hostname    IP&lt;/p&gt;

&lt;p&gt;Node 1  pve1    192.168.1.21&lt;br&gt;
Node 2  pve2    192.168.1.22&lt;br&gt;
Node 3  pve3    192.168.1.23&lt;/p&gt;

&lt;p&gt;DNS points to my Raspberry Pi ✅&lt;/p&gt;

&lt;p&gt;Installation takes a few minutes&lt;br&gt;
and Proxmox is ready through a browser 🎉&lt;/p&gt;

&lt;p&gt;✅ Step 3. Fix NIC Offloading Stability Issues&lt;/p&gt;

&lt;p&gt;This one hit me hard 😬&lt;br&gt;
I kept encountering:&lt;/p&gt;

&lt;p&gt;❌ random node connectivity loss&lt;br&gt;
❌ nodes freezing during network traffic&lt;br&gt;
❌ SSH dropping without reason&lt;/p&gt;

&lt;p&gt;Cause&lt;br&gt;
👉 NIC offloading on Intel network adapters&lt;/p&gt;

&lt;p&gt;Fix using this excellent community script&lt;br&gt;
&lt;a href="https://community-scripts.github.io/ProxmoxVE/scripts?id=nic-offloading-fix" rel="noopener noreferrer"&gt;https://community-scripts.github.io/ProxmoxVE/scripts?id=nic-offloading-fix&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After applying this&lt;br&gt;
the cluster became rock solid ✅&lt;/p&gt;

&lt;p&gt;If you run Lenovo Tiny&lt;br&gt;
this fix is almost mandatory 👀&lt;/p&gt;

&lt;p&gt;✅ Step 4. Create the Proxmox Cluster&lt;/p&gt;

&lt;p&gt;On node pve1:&lt;/p&gt;

&lt;p&gt;pvecm create homelab-cluster&lt;/p&gt;

&lt;p&gt;On other nodes:&lt;/p&gt;

&lt;p&gt;pvecm add 192.168.1.21&lt;/p&gt;

&lt;p&gt;Now&lt;br&gt;
I can manage all nodes from a single interface ✅&lt;br&gt;
VMs can migrate easily between hosts ✅&lt;br&gt;
Monitoring is unified ✅&lt;/p&gt;

&lt;p&gt;Production vibes at home 😎&lt;/p&gt;

&lt;p&gt;✅ Step 5. Flat Network Setup First&lt;/p&gt;

&lt;p&gt;Managed Switch Comes Later&lt;/p&gt;

&lt;p&gt;For now&lt;br&gt;
the network is simple:&lt;/p&gt;

&lt;p&gt;one Gigabit flat network&lt;/p&gt;

&lt;p&gt;vmbr0 bridging the physical NIC&lt;/p&gt;

&lt;p&gt;DHCP/static addressing for VMs&lt;/p&gt;

&lt;p&gt;Benefits&lt;br&gt;
✅ simple&lt;br&gt;
✅ stable&lt;br&gt;
✅ easy to troubleshoot&lt;/p&gt;

&lt;p&gt;Future upgrade:&lt;br&gt;
🔹 Managed PoE switch&lt;br&gt;
🔹 VLAN segmentation for Kubernetes traffic&lt;br&gt;
🔹 Insights and better monitoring&lt;/p&gt;

&lt;p&gt;Great now&lt;br&gt;
but room to grow ✅&lt;/p&gt;

&lt;p&gt;✅ Step 6. Main Storage: Synology NAS (NFS + iSCSI)&lt;/p&gt;

&lt;p&gt;Persistent workloads require persistent storage&lt;br&gt;
so my Synology NAS handles all Kubernetes storage&lt;/p&gt;

&lt;p&gt;✅ NFS for most apps&lt;br&gt;
✅ iSCSI for database style block volumes&lt;br&gt;
✅ RAID and snapshots for safety&lt;br&gt;
✅ Backup target for Proxmox VMs&lt;/p&gt;

&lt;p&gt;The NAS is the data backbone of the homelab&lt;br&gt;
exactly how storage is handled in production 🚀&lt;/p&gt;

&lt;p&gt;✅ Step 7. Automated Ubuntu 25.x Templates With Packer&lt;/p&gt;

&lt;p&gt;To avoid manual VM setup&lt;br&gt;
I automated template building using Packer&lt;/p&gt;

&lt;p&gt;Operating system&lt;br&gt;
🧩 Ubuntu Server 25.x&lt;br&gt;
Modern kernel and great container support&lt;/p&gt;

&lt;p&gt;My Packer template creates VMs that are:&lt;br&gt;
✔ cloud init ready&lt;br&gt;
✔ qemu guest agent installed&lt;br&gt;
✔ ssh enabled&lt;br&gt;
✔ updated and hardened&lt;br&gt;
✔ swap disabled for Kubernetes&lt;/p&gt;

&lt;p&gt;This allows me to deploy&lt;br&gt;
a brand new Kubernetes node VM in under 30 seconds 🤯&lt;/p&gt;

&lt;p&gt;Fast&lt;br&gt;
repeatable&lt;br&gt;
version controlled&lt;/p&gt;

&lt;p&gt;Infrastructure as code for real 😁&lt;/p&gt;

&lt;p&gt;⚠️ Important Real World Gotchas&lt;/p&gt;

&lt;p&gt;Here are issues I personally hit&lt;br&gt;
and how I fixed them ✅&lt;/p&gt;

&lt;p&gt;Problem Cause   Solution&lt;/p&gt;

&lt;p&gt;Node freezes or network drops   NIC offloading  Apply NIC fix ✅&lt;br&gt;
Unexpected reboots or throttling    Old BIOS    Flash BIOS ✅&lt;br&gt;
High CPU temps under load   Small chassis   Tune cooling profile ✅&lt;br&gt;
VMs or pods OOM Overcommit  Reserve RAM for hypervisor ✅&lt;/p&gt;

&lt;p&gt;Homelabs teach you technology&lt;br&gt;
by breaking and fixing it&lt;br&gt;
the best learning path 🔥&lt;/p&gt;

&lt;p&gt;✅ What We Have Achieved So Far&lt;/p&gt;

&lt;p&gt;Feature Status&lt;/p&gt;

&lt;p&gt;Three node Proxmox cluster  ✅&lt;br&gt;
Network stability   ✅&lt;br&gt;
NAS integrated for storage  ✅&lt;br&gt;
Custom VM templates ✅&lt;br&gt;
Automation friendly ✅&lt;br&gt;
Kubernetes ready foundation ✅&lt;/p&gt;

&lt;p&gt;A real mini datacenter is now running in my home&lt;br&gt;
quietly&lt;br&gt;
efficiently&lt;br&gt;
and fully under my control 🏡✨&lt;/p&gt;

&lt;p&gt;✅ Coming Next&lt;/p&gt;

&lt;p&gt;Deploying Kubernetes with Kubespray&lt;/p&gt;

&lt;p&gt;In the next article you will see:&lt;/p&gt;

&lt;p&gt;🔥 3 high availability control plane nodes&lt;br&gt;
🔥 6 workers ready for workloads&lt;br&gt;
🔥 DNS fully internal&lt;br&gt;
🔥 main storage already integrated&lt;br&gt;
🔥 GitOps and monitoring soon after&lt;/p&gt;

&lt;p&gt;This is when the real fun begins 😎🔥&lt;/p&gt;

&lt;p&gt;Stay tuned&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cloud</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>🧰 The Hardware That Powers My Homelab Kubernetes Cluster</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Tue, 28 Oct 2025 09:44:55 +0000</pubDate>
      <link>https://forem.com/hkhelil/the-hardware-that-powers-my-homelab-kubernetes-cluster-45pb</link>
      <guid>https://forem.com/hkhelil/the-hardware-that-powers-my-homelab-kubernetes-cluster-45pb</guid>
      <description>&lt;p&gt;Hey everyone 👋&lt;/p&gt;

&lt;p&gt;Before diving deeper into the software stack of my homelab&lt;br&gt;
I want to fully introduce the physical components that make this whole project come alive 🧑‍🔧&lt;/p&gt;

&lt;p&gt;This article focuses entirely on &lt;strong&gt;hardware choices&lt;/strong&gt; and &lt;strong&gt;why each element matters&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s explore the building blocks of my mini data center 🏡⚡&lt;/p&gt;


&lt;h2&gt;
  
  
  🖥️ The Compute Layer
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Three Lenovo ThinkCentre M920q Tiny
&lt;/h3&gt;

&lt;p&gt;These three machines are the heart of the homelab&lt;br&gt;
They provide the CPU and memory resources needed to run Kubernetes workloads inside virtual machines&lt;/p&gt;

&lt;p&gt;Why I chose them&lt;br&gt;
✅ compact and silent&lt;br&gt;
✅ excellent performance per watt&lt;br&gt;
✅ reliable and low power for 24x7&lt;br&gt;
✅ scalable memory support&lt;/p&gt;

&lt;p&gt;Specs per node&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intel CPU with multiple cores&lt;/li&gt;
&lt;li&gt;32 GB RAM&lt;/li&gt;
&lt;li&gt;NVMe SSD for fast VM storage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together they give me:&lt;br&gt;
🧠 96 GB RAM total&lt;br&gt;
⚡ 36 vCPUs total&lt;/p&gt;

&lt;p&gt;This allows me to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;simulate real production workloads&lt;/li&gt;
&lt;li&gt;run 3 HA control plane nodes&lt;/li&gt;
&lt;li&gt;run 6 worker nodes&lt;/li&gt;
&lt;li&gt;add utility VMs when needed&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small machines&lt;br&gt;
big power 😁&lt;/p&gt;


&lt;h2&gt;
  
  
  🌐 The Networking Layer
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Gigabit Switch Today
&lt;/h3&gt;

&lt;p&gt;Managed PoE Switch Tomorrow&lt;/p&gt;

&lt;p&gt;Right now my network is built on a simple &lt;strong&gt;Gigabit Ethernet switch&lt;/strong&gt;&lt;br&gt;
Flat network&lt;br&gt;
no VLANs&lt;br&gt;
no advanced routing&lt;/p&gt;

&lt;p&gt;This keeps things:&lt;br&gt;
✅ simple to maintain&lt;br&gt;
✅ easy to debug&lt;br&gt;
✅ perfectly functional for a first iteration&lt;/p&gt;

&lt;p&gt;But I am already thinking ahead 👀&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;managed switch with PoE&lt;/strong&gt; is on my radar because it would allow:&lt;br&gt;
🧩 VLAN segmentation for test environments&lt;br&gt;
📡 powering future access points or cameras directly from the switch&lt;br&gt;
🛡️ network isolation for storage, monitoring or nodes&lt;br&gt;
📈 better performance analysis through advanced metrics&lt;/p&gt;

&lt;p&gt;For now&lt;br&gt;
simplicity wins&lt;br&gt;
but I want room to grow&lt;/p&gt;


&lt;h2&gt;
  
  
  🍓 The Brain of the LAN
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Raspberry Pi as DNS Server
&lt;/h3&gt;

&lt;p&gt;DNS is a critical piece in any infrastructure&lt;br&gt;
especially Kubernetes&lt;/p&gt;

&lt;p&gt;The Raspberry Pi handles:&lt;br&gt;
✅ local DNS resolution&lt;br&gt;
✅ service discovery across the home network&lt;br&gt;
✅ potential Pi hole extension later&lt;/p&gt;

&lt;p&gt;It ensures &lt;strong&gt;zero reliance on public DNS&lt;/strong&gt; for internal services&lt;br&gt;
Fast&lt;br&gt;
lightweight&lt;br&gt;
and rock solid 🧩&lt;/p&gt;


&lt;h2&gt;
  
  
  📦 The Data Layer
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Synology NAS for Storage and Backups
&lt;/h3&gt;

&lt;p&gt;Applications need persistent data&lt;br&gt;
databases&lt;br&gt;
monitoring stack&lt;br&gt;
GitOps metadata&lt;/p&gt;

&lt;p&gt;The Synology NAS provides:&lt;br&gt;
✅ NFS shares for general storage&lt;br&gt;
✅ iSCSI volumes for testing block level workloads&lt;br&gt;
✅ snapshots and RAID protection&lt;br&gt;
✅ backup space for Proxmox VMs&lt;/p&gt;

&lt;p&gt;It is the &lt;strong&gt;data backbone&lt;/strong&gt; of the homelab&lt;br&gt;
keeping stateful workloads safe 🧡&lt;/p&gt;


&lt;h2&gt;
  
  
  🔌 Hardware Architecture Diagram
&lt;/h2&gt;

&lt;p&gt;Here is a more complete view of the hardware layout&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Internet
                    │
                 Router
               with VPN
                    │
             ┌──────┴──────┐
             │             │
       Gigabit Switch      │
    (Flat network today    │
     but future Managed PoE)
             │
 ┌───────────┼───────────────┬───────────┐
 │           │               │           │
[Node 1]  [Node 2]        [Node 3]   Synology NAS
Proxmox   Proxmox         Proxmox         
 │         │               │              
 └─────────┴───────────┬───┘              
                       │                  
                Raspberry Pi (DNS)        
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything works together as &lt;strong&gt;a single coherent platform&lt;/strong&gt;&lt;br&gt;
Compute + Networking + DNS + Storage&lt;br&gt;
self hosted and fully controlled ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Why this hardware matters
&lt;/h2&gt;

&lt;p&gt;Every piece of this hardware plays a key role&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hardware&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lenovo Tiny Nodes&lt;/td&gt;
&lt;td&gt;Compute layer for Kubernetes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gigabit Switch&lt;/td&gt;
&lt;td&gt;Fast and reliable local communications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed PoE Switch future&lt;/td&gt;
&lt;td&gt;Network control and segmentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Raspberry Pi&lt;/td&gt;
&lt;td&gt;DNS authority for all internal traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synology NAS&lt;/td&gt;
&lt;td&gt;Persistent data and VM backup storage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This creates a &lt;strong&gt;production style environment&lt;/strong&gt;&lt;br&gt;
ready for real workloads and real failures&lt;br&gt;
without cloud overhead or noise pollution&lt;/p&gt;

&lt;p&gt;A smart and scalable foundation 🤝&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Up Next
&lt;/h2&gt;

&lt;p&gt;Next article in the series&lt;br&gt;
➡️ Turning this hardware into a Proxmox cluster&lt;br&gt;
➡️ VM management&lt;br&gt;
➡️ Template strategy&lt;br&gt;
➡️ Solid virtualization layer for Kubernetes&lt;/p&gt;

&lt;p&gt;Stay tuned&lt;br&gt;
The fun is only beginning 😎&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>cloud</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>🏡 Why I Built My Own Homelab to Run Kubernetes</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Sun, 26 Oct 2025 20:10:21 +0000</pubDate>
      <link>https://forem.com/hkhelil/why-i-built-my-own-homelab-to-run-kubernetes-ke9</link>
      <guid>https://forem.com/hkhelil/why-i-built-my-own-homelab-to-run-kubernetes-ke9</guid>
      <description>&lt;p&gt;Hey everyone 👋&lt;/p&gt;

&lt;p&gt;If you have been following my posts, you might have noticed that I have been pretty quiet lately 😅&lt;br&gt;
There is a good reason for that&lt;/p&gt;

&lt;p&gt;I have been spending a lot of time building my own homelab a place where I can safely test, break, and rebuild complex infrastructures without worrying about cloud costs or production risks 🧑‍💻&lt;/p&gt;

&lt;p&gt;My goal is simple&lt;br&gt;
Create a production grade Kubernetes environment at home using Proxmox for virtualization and Kubespray for cluster provisioning 💡&lt;br&gt;
This environment allows me to test real world scenarios and also experiment with features for my Kubermates project 🚀&lt;/p&gt;

&lt;p&gt;Let me walk you through the why and the how&lt;/p&gt;




&lt;p&gt;⚙️ The Motivation&lt;/p&gt;

&lt;p&gt;As a DevOps and SRE engineer, Kubernetes is part of my daily life.&lt;br&gt;
But testing new tools, scaling strategies, GitOps workflows, or security rules often requires:&lt;/p&gt;

&lt;p&gt;✅ complete freedom&lt;br&gt;
✅ zero fear of downtime&lt;br&gt;
✅ repeatability&lt;br&gt;
✅ controlled costs&lt;/p&gt;

&lt;p&gt;A homelab gives me full control of the stack including: network, storage, virtualization, routing, DNS, automation and resilience strategies 💪&lt;/p&gt;

&lt;p&gt;It rapidly became much more than a hobby. It is now my mini data center&lt;/p&gt;




&lt;p&gt;🧩 Proxmox as the Foundation&lt;/p&gt;

&lt;p&gt;To host everything, I chose Proxmox VE&lt;/p&gt;

&lt;p&gt;Reasons behind this choice:&lt;br&gt;
🖥️ clean and powerful web UI and CLI&lt;br&gt;
⚙️ excellent virtualization and storage integration&lt;br&gt;
🌐 flexible networking and clustering&lt;br&gt;
🔁 snapshots and backup system made for tests and disasters&lt;/p&gt;

&lt;p&gt;Each hypervisor node runs multiple virtual machines that will become control planes, workers or utility nodes like DNS and monitoring services&lt;/p&gt;

&lt;p&gt;Proxmox gives me the freedom to rebuild everything as many times as needed&lt;/p&gt;




&lt;p&gt;☁️ Kubernetes Deployment With Kubespray&lt;/p&gt;

&lt;p&gt;Once the virtual machines were ready, I needed a repeatable approach to deploy Kubernetes.&lt;br&gt;
I selected Kubespray because:&lt;/p&gt;

&lt;p&gt;🔥 it is production proven&lt;br&gt;
🧩 uses Ansible which makes automation easy&lt;br&gt;
🔧 allows deep customization&lt;br&gt;
🔄 supports highly available clusters&lt;/p&gt;

&lt;p&gt;This lets me rebuild the entire cluster in minutes while keeping the configuration under version control&lt;/p&gt;

&lt;p&gt;Perfect for experimentation&lt;/p&gt;




&lt;p&gt;💪 Homelab Specs&lt;/p&gt;

&lt;p&gt;Right now, my homelab is running with:&lt;/p&gt;

&lt;p&gt;🧠 96 GB of RAM&lt;br&gt;
⚡ 36 vCPUs&lt;/p&gt;

&lt;p&gt;This gives enough headroom to simulate:&lt;/p&gt;

&lt;p&gt;Autoscaling and performance constraints&lt;/p&gt;

&lt;p&gt;Monitoring with Prometheus, Loki, Thanos etc&lt;/p&gt;

&lt;p&gt;GitOps flows with Argo CD and Helmfile&lt;/p&gt;

&lt;p&gt;Network isolation and multi tenancy&lt;/p&gt;

&lt;p&gt;Production security patterns&lt;/p&gt;

&lt;p&gt;Computing constraints actually make the optimization challenges even more interesting&lt;/p&gt;




&lt;p&gt;🔬 A Playground for Kubermates&lt;/p&gt;

&lt;p&gt;If you know me, you know I am working on Kubermates&lt;br&gt;
This homelab is now my real life testing ground for the platform&lt;/p&gt;

&lt;p&gt;Here I can:&lt;br&gt;
✅ test onboarding flows and automation patterns&lt;br&gt;
✅ simulate multi tenant setups&lt;br&gt;
✅ validate best practices for deploy and operate&lt;br&gt;
✅ try both cluster and application level policies&lt;/p&gt;

&lt;p&gt;Everything is reproducible and controlled&lt;br&gt;
Exactly what I need to test responsibly&lt;/p&gt;




&lt;p&gt;🌐 Secure Remote Access With WireGuard VPN&lt;/p&gt;

&lt;p&gt;Although everything runs at home, I still want to access the environment securely when I travel or work away from my desk&lt;/p&gt;

&lt;p&gt;For this, I use WireGuard VPN running on a small gateway VM&lt;/p&gt;

&lt;p&gt;Why WireGuard&lt;/p&gt;

&lt;p&gt;🔒 strong encryption&lt;/p&gt;

&lt;p&gt;⚡ ultra fast performance&lt;/p&gt;

&lt;p&gt;🧩 minimal configuration&lt;/p&gt;

&lt;p&gt;📱 clients for Linux, Mac, Windows, iOS and Android&lt;/p&gt;

&lt;p&gt;My access architecture:&lt;/p&gt;

&lt;p&gt;Only VPN traffic can reach the Proxmox cluster and Kubernetes nodes&lt;/p&gt;

&lt;p&gt;No ports are exposed directly to the internet&lt;/p&gt;

&lt;p&gt;Internal DNS and Ingress work exactly as inside the local network&lt;/p&gt;

&lt;p&gt;So I get full access to Grafana, Argo CD, dashboards, kubectl and SSH&lt;br&gt;
as if I was physically at home 🏠&lt;/p&gt;

&lt;p&gt;This keeps everything private and secure&lt;br&gt;
Perfect for a personal lab&lt;/p&gt;




&lt;p&gt;🧠 Key Lessons Learned So Far&lt;/p&gt;

&lt;p&gt;Building Kubernetes at home gives you a deeper understanding of what is behind the cloud curtain&lt;/p&gt;

&lt;p&gt;Main takeaways&lt;/p&gt;

&lt;p&gt;Networking is always trickier than expected 😅&lt;/p&gt;

&lt;p&gt;Local DNS matters more than anyone admits&lt;/p&gt;

&lt;p&gt;Good VM templates save hours of setup time&lt;/p&gt;

&lt;p&gt;Resource constraints teach real FinOps awareness&lt;/p&gt;

&lt;p&gt;Breaking things makes you learn faster&lt;/p&gt;

&lt;p&gt;And the best part&lt;br&gt;
It is incredibly fun&lt;/p&gt;




&lt;p&gt;🚀 What Comes Next&lt;/p&gt;

&lt;p&gt;This is just the beginning&lt;/p&gt;

&lt;p&gt;Upcoming articles in the series&lt;br&gt;
1️⃣ Proxmox hardware and virtualization best practices&lt;br&gt;
2️⃣ Kubespray automation and cluster bootstrapping&lt;br&gt;
3️⃣ DNS and networking architecture for homelabs&lt;br&gt;
4️⃣ GitOps, certificates and observability stack deployment&lt;br&gt;
5️⃣ How to simulate production workloads at home&lt;/p&gt;

&lt;p&gt;The goal&lt;br&gt;
Help anyone build a professional grade Kubernetes experience locally&lt;br&gt;
without cloud billing surprises&lt;/p&gt;

&lt;p&gt;Thanks for reading and stay tuned for more 🔥&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>automation</category>
      <category>cloud</category>
    </item>
    <item>
      <title>🧩 GitHub Actions Composite vs Reusable Workflows</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Fri, 18 Jul 2025 08:41:53 +0000</pubDate>
      <link>https://forem.com/hkhelil/github-actions-composite-vs-reusable-workflows-4bih</link>
      <guid>https://forem.com/hkhelil/github-actions-composite-vs-reusable-workflows-4bih</guid>
      <description>&lt;h2&gt;
  
  
  How to standardize and supercharge your CI/CD pipelines across projects
&lt;/h2&gt;

&lt;p&gt;When your teams manage multiple projects with similar deployment patterns, repeating the same GitHub Actions steps over and over can become tedious, error-prone, and hard to maintain&lt;/p&gt;

&lt;p&gt;Thankfully, GitHub Actions offers two powerful solutions to help &lt;strong&gt;standardize, reuse, and scale your CI/CD pipelines&lt;/strong&gt;: &lt;strong&gt;Composite Actions&lt;/strong&gt; and &lt;strong&gt;Reusable Workflows&lt;/strong&gt;. When used together, they form a clean, modular, and DRY (don’t repeat yourself) CI/CD strategy&lt;/p&gt;

&lt;h2&gt;
  
  
  🔄 Composite Actions vs Reusable Workflows
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🧱 Composite Actions
&lt;/h3&gt;

&lt;p&gt;Composite Actions allow you to group steps (like &lt;code&gt;docker build&lt;/code&gt;, &lt;code&gt;terraform plan&lt;/code&gt;, or &lt;code&gt;trivy scan&lt;/code&gt;) into a reusable component. Think of it like a function.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reusing logic (e.g., build and push images)&lt;/li&gt;
&lt;li&gt;Hiding complex logic from the main workflow&lt;/li&gt;
&lt;li&gt;Keeping workflows minimal and maintainable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use it with:&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./.github/actions/your-action&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔁 Reusable Workflows
&lt;/h3&gt;

&lt;p&gt;Reusable Workflows are full workflows that can be invoked with inputs and secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;High-level CI/CD orchestration (build, test, scan, deploy)&lt;/li&gt;
&lt;li&gt;Enforcing common practices across repositories&lt;/li&gt;
&lt;li&gt;Abstracting full pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use it with:&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;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;org/repo/.github/workflows/your-workflow.yml@ref&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🐳 Example: Docker Build and Scan with Trivy
&lt;/h2&gt;

&lt;p&gt;Here is an example of a &lt;strong&gt;composite action&lt;/strong&gt; to build and push a Docker image to Azure Container Registry (ACR) and scan it using Trivy.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;.github/actions/build-and-scan/action.yml&lt;/code&gt;
&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Docker Build and Push&lt;/span&gt;

&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dockerfile&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Dockerfile&lt;/span&gt;
  &lt;span class="na"&gt;context&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
  &lt;span class="na"&gt;image_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;build_args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;
  &lt;span class="na"&gt;report_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trivy-report&lt;/span&gt;

&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;composite"&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&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;azure/login@v2&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;creds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AZURE_CREDENTIALS }}&lt;/span&gt;

    &lt;span class="pi"&gt;-&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;azure/docker-login@v2&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;login-server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.CONTAINER_REGISTRY }}&lt;/span&gt;
        &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ACR_USERNAME }}&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.ACR_PASSWORD }}&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 and Push Image&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&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;docker build ${{ inputs.build_args }} \&lt;/span&gt;
          &lt;span class="s"&gt;-t ${{ env.CONTAINER_REGISTRY }}/${{ inputs.image_name }}:${{ inputs.tag }} \&lt;/span&gt;
          &lt;span class="s"&gt;-f "${{ inputs.dockerfile }}" "${{ inputs.context }}"&lt;/span&gt;
        &lt;span class="s"&gt;docker push ${{ env.CONTAINER_REGISTRY }}/${{ inputs.image_name }}:${{ inputs.tag }}&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;Scan with Trivy&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;aquasecurity/trivy-action@0.32.0&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;image-ref&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.CONTAINER_REGISTRY&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}/${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;inputs.image_name&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}:${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;inputs.tag&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt;
        &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sarif'&lt;/span&gt;
        &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trivy-results.sarif'&lt;/span&gt;
        &lt;span class="na"&gt;exit-code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&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 SARIF to GitHub Security tab&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;github/codeql-action/upload-sarif@v3&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;sarif_file&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trivy-results.sarif'&lt;/span&gt;
        &lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trivy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage in Workflow
&lt;/h3&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;Build and Scan&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;./.github/actions/build-and-scan&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;image_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
    &lt;span class="na"&gt;tag&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ github.sha }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🌍 Example: Terraform/Terragrunt Validation with Checkov
&lt;/h2&gt;

&lt;p&gt;Now let’s see a &lt;strong&gt;reusable workflow&lt;/strong&gt; to validate Terraform code and scan for misconfigurations using Checkov.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;.github/workflows/infra-check.yml&lt;/code&gt;
&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Infra Validation Workflow&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;string&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;validate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&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/checkout@v4&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;Install Terragrunt and OpenTofu&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;curl -s https://raw.githubusercontent.com/opentofu/opentofu/main/scripts/install.sh | bash&lt;/span&gt;
          &lt;span class="s"&gt;curl -L https://github.com/gruntwork-io/terragrunt/releases/latest/download/terragrunt_linux_amd64 -o terragrunt&lt;/span&gt;
          &lt;span class="s"&gt;chmod +x terragrunt&lt;/span&gt;
          &lt;span class="s"&gt;sudo mv terragrunt /usr/local/bin/&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;Validate HCL Syntax&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;terragrunt hclfmt --terragrunt-check --terragrunt-diff --recursive&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.working_dir }}&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 Misconfigurations with Checkov&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;bridgecrewio/checkov-action@v12&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;directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.working_dir }}&lt;/span&gt;
          &lt;span class="na"&gt;quiet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Usage in Project Workflow
&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;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;call-validation&lt;/span&gt;&lt;span class="pi"&gt;:&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;your-org/your-repo/.github/workflows/infra-check.yml@main&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;working_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infrastructure/staging/eu-west-1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ⚙️ Best Practices
&lt;/h2&gt;

&lt;p&gt;✅ Keep composite actions in &lt;code&gt;.github/actions&lt;/code&gt;&lt;br&gt;
✅ Keep reusable workflows in &lt;code&gt;.github/workflows&lt;/code&gt;&lt;br&gt;
✅ Abstract frequently repeated patterns&lt;br&gt;
✅ Use inputs, outputs, and env vars to increase flexibility&lt;br&gt;
✅ Use &lt;code&gt;workflow_call&lt;/code&gt; and &lt;code&gt;workflow_dispatch&lt;/code&gt; to structure triggers&lt;br&gt;
✅ Defer secrets to the calling workflow&lt;/p&gt;

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

&lt;p&gt;Using &lt;strong&gt;composite actions&lt;/strong&gt; and &lt;strong&gt;reusable workflows&lt;/strong&gt; together can transform your CI/CD processes into clean, repeatable, and secure pipelines. Whether you're building containers, provisioning infrastructure, or scanning for vulnerabilities, this modular approach will scale with your teams and your systems.&lt;/p&gt;

&lt;p&gt;Enjoy building better pipelines !&lt;/p&gt;

</description>
      <category>githubactions</category>
      <category>github</category>
      <category>cicd</category>
      <category>automation</category>
    </item>
    <item>
      <title>🚀🌐 Elevating Infrastructure: From Terraform/Terragrunt Foundations to Platform Engineering 😊</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Fri, 18 Apr 2025 10:24:24 +0000</pubDate>
      <link>https://forem.com/hkhelil/elevating-infrastructure-from-terraformterragrunt-foundations-to-platform-engineering-41fc</link>
      <guid>https://forem.com/hkhelil/elevating-infrastructure-from-terraformterragrunt-foundations-to-platform-engineering-41fc</guid>
      <description>&lt;p&gt;Hey there, cloud adventurers! 🚀 Let’s chat about why keeping &lt;strong&gt;Terraform&lt;/strong&gt; (or &lt;strong&gt;OpenTofu&lt;/strong&gt;) and &lt;strong&gt;Terragrunt&lt;/strong&gt; in their own lanes is absolutely essential—and how using &lt;strong&gt;Terraform JSON tfvars&lt;/strong&gt; makes life easier when you’re building nifty tools on top. Ready? Let’s dive in! 😄&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Separation Is a Must, Not an Option 🙅‍♂️🙅‍♀️
&lt;/h3&gt;

&lt;p&gt;It might be tempting to mix Terraform and Terragrunt into one big file—after all, they work together, right? But trust me, keeping them decoupled is a game‑changer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;🔒 Clear boundaries&lt;/strong&gt;: Terraform focuses purely on &lt;em&gt;resource definitions&lt;/em&gt;, while Terragrunt handles &lt;em&gt;orchestration&lt;/em&gt;, remote state, and DRY patterns.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;🔄 Independent versioning&lt;/strong&gt;: You can upgrade your Terraform (or OpenTofu) modules (semantic versioning FTW!) without touching your Terragrunt configs—and vice versa!
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;📦 Reusability everywhere&lt;/strong&gt;: Modules stay generic and shareable across projects when they don’t carry Terragrunt baggage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By giving each tool its own space, you simplify CI/CD, make upgrades safer, and keep responsibilities crystal‑clear. Win‑win! 🏆&lt;/p&gt;

&lt;h3&gt;
  
  
  Module Versioning with Terraform 🎯
&lt;/h3&gt;

&lt;p&gt;Hosting Terraform (or OpenTofu) modules in a versioned repo (GitHub, GitLab, private registry—you choose!) lets you tag releases like &lt;code&gt;v1.2.3&lt;/code&gt;. Then, Terragrunt can lock to that exact version:&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="c1"&gt;// modules/storage-account/main.tf&lt;/span&gt;
&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_storage_account"&lt;/span&gt; &lt;span class="s2"&gt;"sa"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_name&lt;/span&gt;
  &lt;span class="nx"&gt;resource_group_name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resource_group_name&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt;                 &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
  &lt;span class="nx"&gt;account_tier&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Standard"&lt;/span&gt;
  &lt;span class="nx"&gt;account_replication_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"LRS"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"account_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"resource_group_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"location"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// modules/storage-account/versions.tf&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;required_providers&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;azurerm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;source&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"hashicorp/azurerm"&lt;/span&gt;
      &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 3.0"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;required_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"&amp;gt;= 1.5"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Publishing this as, say,&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;git&lt;/span&gt;&lt;span class="err"&gt;::&lt;/span&gt;&lt;span class="nx"&gt;ssh&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//git@github.com/myorg/terraform-modules-repo.git//storage-account?ref=v1.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;means everyone downstream knows exactly what they’re getting—no surprises! 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Decoupling Terraform (or OpenTofu) &amp;amp; Terragrunt 🛠️
&lt;/h3&gt;

&lt;p&gt;For true decoupling, &lt;strong&gt;host your Terraform/OpenTofu modules in a separate Git repository&lt;/strong&gt;, each with its own lifecycle, version tags, and release process. Then reference those modules from Terragrunt using Git sources. This keeps module development and environment orchestration fully independent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example Terragrunt config:&lt;/strong&gt;&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="c1"&gt;# live/dev/terragrunt.hcl&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::ssh://git@github.com/myorg/terraform-modules-repo.git//storage-account?ref=v1.0.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${get_terragrunt_dir()}/variables.tfvars.json"&lt;/span&gt;&lt;span class="err"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;The modules repository evolves on its own schedule.
&lt;/li&gt;
&lt;li&gt;Terragrunt configs in &lt;code&gt;live/&lt;/code&gt; reference specific module versions via Git source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This pattern prevents accidental cross‑pollination of concerns, making upgrades, reviews, and rollbacks a breeze. ✨&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Terraform JSON &lt;code&gt;tfvars&lt;/code&gt; Rocks 📑✨
&lt;/h3&gt;

&lt;p&gt;Sure, HCL is human‑friendly, but &lt;strong&gt;JSON tfvars&lt;/strong&gt; shine when you need to build tools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;🤖 Machine‑readable&lt;/strong&gt;: Every language can parse JSON out of the box—no extra libraries needed!
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;✔️ Schema validation&lt;/strong&gt;: Hook up a JSON Schema to catch mistakes before you even hit “apply.”
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;💡 Dynamic generation&lt;/strong&gt;: Your self‑service portal or CLI can spin out a JSON file in milliseconds.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Example &lt;code&gt;variables.tfvars.json&lt;/code&gt;:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"account_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"myappstorage-${env}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource_group_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"rg-${env}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eastus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tags"&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;"environment"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"platform-team"&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;And Terragrunt gobbles it up with:&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="c1"&gt;# live/dev/terragrunt.hcl&lt;/span&gt;
&lt;span class="nx"&gt;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"git::ssh://git@github.com/myorg/terraform-modules-repo.git//storage-account?ref=v1.0.0"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsondecode&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="err"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"${get_terragrunt_dir()}/variables.tfvars.json"&lt;/span&gt;&lt;span class="err"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See how clean that is? 🥳&lt;/p&gt;

&lt;h3&gt;
  
  
  Building Your Own Abstraction Tools 🧰
&lt;/h3&gt;

&lt;p&gt;Looking to give your team a self‑service experience? Here are two solid approaches:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. CLI with Go &amp;amp; Cobra ⚙️&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast scaffolding&lt;/strong&gt;: Cobra generates a project structure with commands, subcommands, and flag parsing out of the box.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type safety &amp;amp; performance&lt;/strong&gt;: Go’s static typing ensures reliable flag handling, and compiled binaries run blazingly fast.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin architecture&lt;/strong&gt;: Easily hook in custom modules—for example, a command like &lt;code&gt;infra gen&lt;/code&gt; that generates Terraform JSON tfvars files for your Terragrunt inputs.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Example snippet:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;  &lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

  &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"io/ioutil"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/spf13/cobra"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;accountName&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;resourceGroupName&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;location&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;env&lt;/span&gt;               &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;owner&lt;/span&gt;             &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;varsFile&lt;/span&gt;          &lt;span class="kt"&gt;string&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;rootCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"infra"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Self-service infra CLI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;genCmd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="s"&gt;"gen"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Short&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Generate Terraform tfvars JSON file"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;RunE&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;cobra&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}{&lt;/span&gt;
        &lt;span class="s"&gt;"account_name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;        &lt;span class="n"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"resource_group_name"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="s"&gt;"tags"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="s"&gt;"environment"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="s"&gt;"owner"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;owner&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="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MarshalIndent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"  "&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ioutil&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;varsFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0644&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="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;genCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;accountName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"account-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Azure storage account name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;genCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;resourceGroupName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"resource-group"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Resource group name"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;genCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"location"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"eastus"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Azure location"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;genCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;env&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Deployment environment"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;genCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"owner"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"platform-team"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Resource owner"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;genCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flags&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StringVar&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;varsFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"out"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"variables.tfvars.json"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Output JSON tfvars file"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;rootCmd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;genCmd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Self‑service portal with Backstage 🎭&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Unified developer portal&lt;/strong&gt;: Backstage by Spotify lets you surface docs, tooling, and pipelines in one UI.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code plugin&lt;/strong&gt;: Create a custom Backstage plugin to list available Terraform/OpenTofu modules and trigger Terragrunt runs with JSON inputs.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy &amp;amp; approval workflows&lt;/strong&gt;: Integrate with existing Identity/Access tools and GitOps pipelines for reviews before provisioning.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich catalog&lt;/strong&gt;: Use Backstage’s software catalog to track module versions, show metadata (owner, tags, docs), and link to Git repos.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Combine these approaches with strict Terraform/Terragrunt separation and JSON tfvars, and you’ll empower your team with both CLI efficiency and a polished web portal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping Up 🎁
&lt;/h3&gt;

&lt;p&gt;Separating Terraform (or OpenTofu) modules from Terragrunt configs isn’t just a “nice to have”—it’s &lt;em&gt;essential&lt;/em&gt; for maintainable, scalable infrastructure. Pair that with JSON tfvars, and you’re set to build amazing tooling that your whole team will love. Happy provisioning! 🌟&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>terraform</category>
      <category>cloudnative</category>
      <category>terragrunt</category>
    </item>
    <item>
      <title>🔐 Secure Secret Management with SOPS in Helm 🚀</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Thu, 27 Feb 2025 08:15:10 +0000</pubDate>
      <link>https://forem.com/hkhelil/secure-secret-management-with-sops-in-helm-1940</link>
      <guid>https://forem.com/hkhelil/secure-secret-management-with-sops-in-helm-1940</guid>
      <description>&lt;p&gt;When managing applications deployed on Kubernetes, keeping secrets safe while still making them accessible to Helm charts is a challenge. Storing secrets in plaintext is a &lt;strong&gt;security risk&lt;/strong&gt; 🚨 — and that’s where &lt;strong&gt;SOPS&lt;/strong&gt; (Secrets OPerationS) and the &lt;strong&gt;Helm Secrets plugin&lt;/strong&gt; come in!&lt;/p&gt;

&lt;p&gt;In this guide, we’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ How to use &lt;strong&gt;SOPS&lt;/strong&gt; with &lt;strong&gt;age&lt;/strong&gt; and &lt;strong&gt;GPG&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ How to configure &lt;strong&gt;SOPS with &lt;code&gt;sops.yaml&lt;/code&gt;&lt;/strong&gt; for better management&lt;/li&gt;
&lt;li&gt;✅ How to use &lt;strong&gt;Helm Secrets Plugin&lt;/strong&gt; to manage encrypted secrets directly in your Helm charts&lt;/li&gt;
&lt;li&gt;✅ A &lt;strong&gt;GitHub Actions workflow&lt;/strong&gt; to securely deploy Helm charts using encrypted secrets&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📌 Why Use SOPS with Helm?
&lt;/h2&gt;

&lt;p&gt;SOPS is an open-source tool from Mozilla that lets you &lt;strong&gt;encrypt and decrypt&lt;/strong&gt; secrets with ease. When combined with the Helm Secrets plugin, you can safely store your sensitive data in Git repositories and automatically decrypt them during Helm deployments. Here’s why it’s awesome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Keeps secrets encrypted in your repos
&lt;/li&gt;
&lt;li&gt;✅ Works with YAML, JSON, and ENV files
&lt;/li&gt;
&lt;li&gt;✅ Integrates seamlessly with Helm via the &lt;strong&gt;Helm Secrets plugin&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ Fits perfectly into CI/CD pipelines like &lt;strong&gt;GitHub Actions&lt;/strong&gt; for secure deployments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🔑 Using SOPS with &lt;code&gt;age&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/FiloSottile/age" rel="noopener noreferrer"&gt;Age&lt;/a&gt; is a modern, simple, and secure encryption tool. If you’re new to encryption, &lt;strong&gt;age is a great alternative to GPG&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 1: Install &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;sops&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;Install &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;sops&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;age    &lt;span class="c"&gt;# Ubuntu/Debian&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✨ Step 2: Generate an &lt;code&gt;age&lt;/code&gt; Key
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;age-keygen &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.config/sops/age/keys.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a key similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# public key: age1xxxxxxx
AGE-SECRET-KEY-1XXXXXXYYYYYYYYZZZZZZ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the &lt;strong&gt;public key&lt;/strong&gt; (&lt;code&gt;age1xxxxxxx&lt;/code&gt;)—this will be used for encryption.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 3: Encrypt a YAML File with SOPS
&lt;/h3&gt;

&lt;p&gt;Create a file called &lt;code&gt;secrets.yaml&lt;/code&gt;:&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;db_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin"&lt;/span&gt;
&lt;span class="na"&gt;db_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;supersecret"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, encrypt it using SOPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sops &lt;span class="nt"&gt;--encrypt&lt;/span&gt; &lt;span class="nt"&gt;--age&lt;/span&gt; age1xxxxxxx &lt;span class="nt"&gt;-i&lt;/span&gt; secrets.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you open &lt;code&gt;secrets.yaml&lt;/code&gt;, you’ll see it’s fully encrypted! 🛡️&lt;/p&gt;

&lt;p&gt;To &lt;strong&gt;decrypt&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sops &lt;span class="nt"&gt;--decrypt&lt;/span&gt; secrets.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔧 Configuring &lt;code&gt;sops.yaml&lt;/code&gt; for Better Management
&lt;/h2&gt;

&lt;p&gt;Instead of specifying the encryption method manually every time, SOPS supports a configuration file (&lt;code&gt;.sops.yaml&lt;/code&gt;). This makes it easier to manage secrets across your team.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.sops.yaml&lt;/code&gt; in your repository:&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;creation_rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path_regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets/.*\.yaml$&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;age1xxxxxxx&lt;/span&gt;  &lt;span class="c1"&gt;# Replace with your public key&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path_regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets/.*\.json$&lt;/span&gt;
    &lt;span class="na"&gt;pgp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ABC12345&lt;/span&gt;  &lt;span class="c1"&gt;# Replace with your GPG key ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when encrypting secrets inside the &lt;code&gt;secrets/&lt;/code&gt; folder, SOPS will &lt;strong&gt;automatically&lt;/strong&gt; use the right encryption method! 🎉&lt;/p&gt;

&lt;p&gt;Encrypt a new secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sops &lt;span class="nt"&gt;--encrypt&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; secrets/app.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🛠️ Using Helm with the Helm Secrets Plugin
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;Helm Secrets plugin&lt;/strong&gt; allows you to work with encrypted secrets directly in your Helm charts—no need to expose sensitive data!&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 1: Install the Helm Secrets Plugin
&lt;/h3&gt;

&lt;p&gt;Install the plugin with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm plugin &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/jkroepke/helm-secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This plugin leverages SOPS to decrypt your secret files during Helm chart deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 2: Encrypt Your Secrets File
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;secrets.yaml&lt;/code&gt; (if you haven’t already):&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;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;Secret&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;my-secret&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;Opaque&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;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;YWRtaW4=&lt;/span&gt;          &lt;span class="c1"&gt;# base64 encoded&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;c3VwZXJzZWNyZXQ=&lt;/span&gt;   &lt;span class="c1"&gt;# base64 encoded&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Encrypt it using SOPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sops &lt;span class="nt"&gt;--encrypt&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; secrets.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✨ Step 3: Deploy with Helm Using Encrypted Secrets
&lt;/h3&gt;

&lt;p&gt;Deploy your Helm chart using the encrypted secrets file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm secrets upgrade &lt;span class="nt"&gt;--install&lt;/span&gt; my-release ./my-chart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Helm Secrets plugin will automatically decrypt &lt;code&gt;secrets.yaml&lt;/code&gt; during the deployment process. 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 Using SOPS and Helm in GitHub Actions
&lt;/h2&gt;

&lt;p&gt;Integrate your secure secrets management into your CI/CD pipeline with GitHub Actions. Here’s an example workflow that deploys your Helm chart with encrypted secrets:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 1: Store the &lt;code&gt;age&lt;/code&gt; Private Key in GitHub Secrets
&lt;/h3&gt;

&lt;p&gt;In your GitHub repository, navigate to &lt;strong&gt;Settings → Secrets and variables → Actions&lt;/strong&gt;, and add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SOPS_AGE_KEY&lt;/code&gt;: The &lt;strong&gt;private key&lt;/strong&gt; from &lt;code&gt;~/.config/sops/age/keys.txt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✨ Step 2: Create the GitHub Actions Workflow
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt;:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy with Helm &amp;amp; SOPS&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&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;Checkout repository&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/checkout@v4&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;Install dependencies&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;sudo apt-get update&lt;/span&gt;
          &lt;span class="s"&gt;sudo apt-get install -y sops age&lt;/span&gt;
          &lt;span class="s"&gt;curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash&lt;/span&gt;
          &lt;span class="s"&gt;helm plugin install https://github.com/jkroepke/helm-secrets&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;Set up SOPS&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;mkdir -p ~/.config/sops/age/&lt;/span&gt;
          &lt;span class="s"&gt;echo "${{ secrets.SOPS_AGE_KEY }}" &amp;gt; ~/.config/sops/age/keys.txt&lt;/span&gt;
          &lt;span class="s"&gt;chmod 600 ~/.config/sops/age/keys.txt&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;Deploy with Helm&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;helm secrets upgrade --install my-release ./my-chart&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔥 What Happens in This Workflow?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Checks out the code&lt;/strong&gt; ✅
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installs SOPS, age, Helm, and the Helm Secrets plugin&lt;/strong&gt; ✅
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loads the age private key from GitHub Secrets&lt;/strong&gt; ✅
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deploys the Helm chart&lt;/strong&gt; with decrypted secrets on the fly ✅
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Security Tip:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Make sure that any decrypted files are never committed to your repository! Always keep them out of version control. 🔒&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Wrapping Up
&lt;/h2&gt;

&lt;p&gt;SOPS and the Helm Secrets plugin offer a &lt;strong&gt;powerful&lt;/strong&gt; and &lt;strong&gt;secure&lt;/strong&gt; way to manage secrets in your Kubernetes deployments. With &lt;strong&gt;age&lt;/strong&gt; encryption, a handy &lt;code&gt;.sops.yaml&lt;/code&gt; configuration, and seamless integration via Helm, managing secrets has never been easier! 💪&lt;/p&gt;

&lt;p&gt;By integrating these tools into your workflow, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Encrypted secrets&lt;/strong&gt; safely stored in Git repositories
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Automatic decryption&lt;/strong&gt; during Helm deployments
&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Secure usage&lt;/strong&gt; of secrets in CI/CD pipelines&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to take it a step further? Try exploring &lt;strong&gt;AWS KMS, GCP KMS, or Azure Key Vault&lt;/strong&gt; for even tighter security! 🔐🚀&lt;/p&gt;

&lt;p&gt;Have questions or suggestions? Drop them in the comments! 💬&lt;/p&gt;

&lt;p&gt;Happy clustering and stay safe! 🔐😊&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🔐 Secure Secret Management with SOPS in Terraform &amp; Terragrunt</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Wed, 26 Feb 2025 14:44:59 +0000</pubDate>
      <link>https://forem.com/hkhelil/secure-secret-management-with-sops-in-terraform-terragrunt-231a</link>
      <guid>https://forem.com/hkhelil/secure-secret-management-with-sops-in-terraform-terragrunt-231a</guid>
      <description>&lt;p&gt;When managing infrastructure as code (IaC), keeping secrets &lt;strong&gt;safe&lt;/strong&gt; while still making them accessible to Terraform/Terragrunt is a challenge. Storing secrets in plaintext is a &lt;strong&gt;security risk&lt;/strong&gt; 🚨—and that’s where &lt;strong&gt;SOPS&lt;/strong&gt; (Secrets OPerationS) comes in!&lt;/p&gt;

&lt;p&gt;In this guide, we’ll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ How to use &lt;strong&gt;SOPS&lt;/strong&gt; with &lt;strong&gt;age&lt;/strong&gt; and &lt;strong&gt;GPG&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;✅ How to configure &lt;strong&gt;SOPS with &lt;code&gt;sops.yaml&lt;/code&gt;&lt;/strong&gt; for better management&lt;/li&gt;
&lt;li&gt;✅ How to use &lt;strong&gt;Terragrunt’s built-in SOPS decryption&lt;/strong&gt; (without &lt;code&gt;run_cmd&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;✅ A &lt;strong&gt;GitHub Actions workflow&lt;/strong&gt; to securely use secrets in CI/CD&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  📌 Why Use SOPS?
&lt;/h2&gt;

&lt;p&gt;SOPS is an open-source tool from Mozilla that lets you &lt;strong&gt;encrypt and decrypt&lt;/strong&gt; secrets easily. It supports multiple encryption methods, including &lt;strong&gt;GPG&lt;/strong&gt;, &lt;strong&gt;AWS KMS&lt;/strong&gt;, &lt;strong&gt;Azure Key Vault&lt;/strong&gt;, &lt;strong&gt;Google Cloud KMS&lt;/strong&gt;, and &lt;strong&gt;age&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s why it’s awesome:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Keeps secrets encrypted in Git repositories&lt;/li&gt;
&lt;li&gt;✅ Works with YAML, JSON, ENV files&lt;/li&gt;
&lt;li&gt;✅ Has built-in support in &lt;strong&gt;Terragrunt&lt;/strong&gt; (no extra scripting needed!)&lt;/li&gt;
&lt;li&gt;✅ Integrates with &lt;strong&gt;GitHub Actions&lt;/strong&gt;, &lt;strong&gt;Kubernetes&lt;/strong&gt;, and &lt;strong&gt;CI/CD pipelines&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, let’s see how to use &lt;strong&gt;SOPS with &lt;code&gt;age&lt;/code&gt; and GPG&lt;/strong&gt;, then configure it properly for &lt;strong&gt;Terragrunt and GitHub Actions&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  🔑 Using SOPS with &lt;code&gt;age&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/FiloSottile/age" rel="noopener noreferrer"&gt;Age&lt;/a&gt; is a modern, simple, and secure encryption tool. If you’re new to encryption, &lt;strong&gt;age is a great alternative to GPG&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 1: Install &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;sops&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;First, install &lt;code&gt;age&lt;/code&gt; and &lt;code&gt;sops&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;age    &lt;span class="c"&gt;# Ubuntu/Debian&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✨ Step 2: Generate an &lt;code&gt;age&lt;/code&gt; Key
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;age-keygen &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.config/sops/age/keys.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate a key similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# public key: age1xxxxxxx
AGE-SECRET-KEY-1XXXXXXYYYYYYYYZZZZZZ
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the &lt;strong&gt;public key&lt;/strong&gt; (&lt;code&gt;age1xxxxxxx&lt;/code&gt;)—this will be used for encryption.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 3: Encrypt a YAML File with SOPS
&lt;/h3&gt;

&lt;p&gt;Create a file called &lt;code&gt;secrets.yaml&lt;/code&gt;:&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;db_user&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;admin"&lt;/span&gt;
&lt;span class="na"&gt;db_password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;supersecret"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, encrypt it using SOPS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sops &lt;span class="nt"&gt;--encrypt&lt;/span&gt; &lt;span class="nt"&gt;--age&lt;/span&gt; age1xxxxxxx &lt;span class="nt"&gt;-i&lt;/span&gt; secrets.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you open &lt;code&gt;secrets.yaml&lt;/code&gt;, you'll see it's fully encrypted! 🛡️&lt;/p&gt;

&lt;p&gt;To &lt;strong&gt;decrypt&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sops &lt;span class="nt"&gt;--decrypt&lt;/span&gt; secrets.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  🔧 Configuring &lt;code&gt;sops.yaml&lt;/code&gt; for Better Management
&lt;/h2&gt;

&lt;p&gt;Instead of specifying the encryption method manually every time, &lt;strong&gt;SOPS supports a configuration file&lt;/strong&gt; (&lt;code&gt;.sops.yaml&lt;/code&gt;). This makes it easier to manage secrets across teams.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;.sops.yaml&lt;/code&gt; in your repository:&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;creation_rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path_regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets/.*\.yaml$&lt;/span&gt;
    &lt;span class="na"&gt;age&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;age1xxxxxxx&lt;/span&gt;  &lt;span class="c1"&gt;# Replace with your public key&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path_regex&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets/.*\.json$&lt;/span&gt;
    &lt;span class="na"&gt;pgp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ABC12345&lt;/span&gt;  &lt;span class="c1"&gt;# Replace with your GPG key ID&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, when encrypting secrets inside the &lt;code&gt;secrets/&lt;/code&gt; folder, SOPS will &lt;strong&gt;automatically&lt;/strong&gt; use the right encryption method! 🎉&lt;/p&gt;

&lt;p&gt;Encrypt a new secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sops &lt;span class="nt"&gt;--encrypt&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; secrets/app.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ⚙️ Using SOPS with Terragrunt’s Built-in Decryption
&lt;/h2&gt;

&lt;p&gt;Terragrunt has &lt;strong&gt;native support for SOPS&lt;/strong&gt;, meaning you don’t need to use &lt;code&gt;run_cmd()&lt;/code&gt;. Instead, you can &lt;strong&gt;directly reference encrypted files&lt;/strong&gt; in your &lt;code&gt;terragrunt.hcl&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 1: Encrypt the Secrets
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;secrets.yaml&lt;/code&gt;:&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;aws_access_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AKIAxxxxxxxxxxxx"&lt;/span&gt;
&lt;span class="na"&gt;aws_secret_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;abcdefghijklmno1234567890"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Encrypt it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sops &lt;span class="nt"&gt;--encrypt&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; secrets.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✨ Step 2: Use Terragrunt's Built-in SOPS Decryption
&lt;/h3&gt;

&lt;p&gt;Modify &lt;code&gt;terragrunt.hcl&lt;/code&gt;:&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;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;secrets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yamldecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sops_decrypt_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"secrets.yaml"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;inputs&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;aws_access_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_access_key&lt;/span&gt;
  &lt;span class="nx"&gt;aws_secret_key&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_secret_key&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, 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;terragrunt apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terragrunt &lt;strong&gt;automatically decrypts&lt;/strong&gt; &lt;code&gt;secrets.yaml&lt;/code&gt; without requiring external scripts! 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  🤖 Using SOPS in GitHub Actions
&lt;/h2&gt;

&lt;p&gt;When using GitHub Actions, we need to &lt;strong&gt;decrypt secrets safely&lt;/strong&gt; without exposing them.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✨ Step 1: Store the &lt;code&gt;age&lt;/code&gt; Private Key in GitHub Secrets
&lt;/h3&gt;

&lt;p&gt;Go to &lt;strong&gt;GitHub → Your Repo → Settings → Secrets and variables → Actions&lt;/strong&gt;, and add:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;SOPS_AGE_KEY&lt;/code&gt;: The &lt;strong&gt;private key&lt;/strong&gt; from &lt;code&gt;~/.config/sops/age/keys.txt&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✨ Step 2: Use SOPS in a GitHub Workflow
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;.github/workflows/deploy.yml&lt;/code&gt;:&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy with Terraform &amp;amp; SOPS&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&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;Checkout repository&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/checkout@v4&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;Install dependencies&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;sudo apt-get update&lt;/span&gt;
          &lt;span class="s"&gt;sudo apt-get install -y sops age&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;Set up SOPS&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;mkdir -p ~/.config/sops/age/&lt;/span&gt;
          &lt;span class="s"&gt;echo "${{ secrets.SOPS_AGE_KEY }}" &amp;gt; ~/.config/sops/age/keys.txt&lt;/span&gt;
          &lt;span class="s"&gt;chmod 600 ~/.config/sops/age/keys.txt&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;Decrypt Secrets&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;sops --decrypt secrets.yaml &amp;gt; secrets.decrypted.yaml&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;Deploy with Terraform&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;terragrunt run-all apply --auto-approve&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔥 What Happens in This Workflow?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Checks out the code&lt;/strong&gt; ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installs SOPS &amp;amp; age&lt;/strong&gt; ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Loads the age private key from GitHub Secrets&lt;/strong&gt; ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decrypts secrets&lt;/strong&gt; into a temporary file ✅&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Runs Terraform/Terragrunt&lt;/strong&gt; with the decrypted secrets ✅&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Security Tip:&lt;/strong&gt;&lt;br&gt;
Make sure &lt;strong&gt;secrets.decrypted.yaml&lt;/strong&gt; is ignored in &lt;code&gt;.gitignore&lt;/code&gt; and is &lt;strong&gt;never committed&lt;/strong&gt; to Git!&lt;/p&gt;

&lt;h2&gt;
  
  
  🎯 Wrapping Up
&lt;/h2&gt;

&lt;p&gt;SOPS is a &lt;strong&gt;powerful&lt;/strong&gt; and &lt;strong&gt;secure&lt;/strong&gt; way to manage secrets for Terraform, Terragrunt, and GitHub Actions. With &lt;strong&gt;age&lt;/strong&gt; encryption, &lt;code&gt;.sops.yaml&lt;/code&gt; for better configuration, and &lt;strong&gt;Terragrunt's built-in decryption&lt;/strong&gt;, managing secrets has never been easier! 💪&lt;/p&gt;

&lt;p&gt;By integrating SOPS into your workflow, you get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Encrypted secrets&lt;/strong&gt; in Git repositories&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Easy decryption&lt;/strong&gt; in Terraform/Terragrunt&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Safe usage of secrets in CI/CD&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Want to take it a step further? Try using &lt;strong&gt;AWS KMS, GCP KMS, or Azure Key Vault&lt;/strong&gt; instead of age/GPG for even tighter security! 🔐🚀&lt;/p&gt;

&lt;p&gt;Have questions or suggestions? Drop them in the comments! 💬&lt;/p&gt;

&lt;p&gt;Happy clustering and stay safe ! 🔐&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>security</category>
      <category>azure</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Helm Chart Essentials &amp; Writing Effective Charts 🚀</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Thu, 23 Jan 2025 10:50:53 +0000</pubDate>
      <link>https://forem.com/hkhelil/helm-chart-essentials-writing-effective-charts-11ca</link>
      <guid>https://forem.com/hkhelil/helm-chart-essentials-writing-effective-charts-11ca</guid>
      <description>&lt;p&gt;Helm charts are a powerful way to define, install, and upgrade Kubernetes applications. By packaging all the Kubernetes manifests and parameters in a neat, reproducible format, Helm simplifies the deployment process for engineers and DevOps teams. In this article, we’ll explore some best practices for writing effective Helm charts, introduce the &lt;strong&gt;Helm Schema plugin&lt;/strong&gt; for validation, show how to include tests to ensure reliability, discuss &lt;strong&gt;helm-docs&lt;/strong&gt; for automated documentation generation, and share an additional resource for testing and linting. Let’s get started! 🎉&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Getting Started with Helm Charts 🏁
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Is Helm?
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://helm.sh/" rel="noopener noreferrer"&gt;Helm&lt;/a&gt; is a package manager for Kubernetes, similar to how apt/yum/brew work for operating systems. It helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Package&lt;/strong&gt; your Kubernetes resources into self-contained units called &lt;em&gt;charts&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install&lt;/strong&gt; and &lt;strong&gt;upgrade&lt;/strong&gt; your applications with version tracking.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplify&lt;/strong&gt; complex deployments by parameterizing configurations in YAML.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Anatomy of a Helm Chart
&lt;/h3&gt;

&lt;p&gt;A typical Helm chart includes the following structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;my-awesome-chart/
├── Chart.yaml
├── values.yaml
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   └── tests/
│       └── test-connection.yaml
└── charts/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Chart.yaml&lt;/strong&gt;: Contains metadata about your chart, like name and version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;values.yaml&lt;/strong&gt;: Holds the default values your chart will use unless overridden by the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;templates/&lt;/strong&gt;: Contains Kubernetes manifests that get rendered using the values specified in &lt;code&gt;values.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;charts/&lt;/strong&gt;: A directory to hold other charts your chart depends on (sub-charts).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;tests/&lt;/strong&gt;: A folder inside &lt;code&gt;templates/&lt;/code&gt; where you can define chart tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Best Practices for Writing Effective Helm Charts 🏆
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Follow a Consistent Structure&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Keeping a clean chart directory structure helps others (and your future self) navigate easily.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Version Management&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Update &lt;code&gt;version&lt;/code&gt; in &lt;code&gt;Chart.yaml&lt;/code&gt; according to &lt;a href="https://semver.org/" rel="noopener noreferrer"&gt;SemVer principles&lt;/a&gt;.
&lt;/li&gt;
&lt;li&gt;Keep your &lt;code&gt;appVersion&lt;/code&gt; (in &lt;code&gt;Chart.yaml&lt;/code&gt;) aligned with the application’s version it deploys.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Leverage &lt;code&gt;values.yaml&lt;/code&gt;&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Store user-configurable defaults in &lt;code&gt;values.yaml&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Use clear naming to describe each parameter.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use Helpers and Templates&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create &lt;code&gt;_helpers.tpl&lt;/code&gt; for small, reusable snippets (like labels or names).
&lt;/li&gt;
&lt;li&gt;Standardize your resource naming (e.g., &lt;code&gt;{{ include "my-awesome-chart.fullname" . }}&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Document Everything&lt;/strong&gt;  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Add a &lt;code&gt;README.md&lt;/code&gt; with usage instructions, examples, and default values.
&lt;/li&gt;
&lt;li&gt;Provide context for each parameter in &lt;code&gt;values.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Following these tips ensures your charts stay consistent, maintainable, and user-friendly. 🙌&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Introducing the Helm Schema Plugin 📐
&lt;/h2&gt;

&lt;p&gt;When you have multiple parameters and complex configurations, validating your &lt;code&gt;values.yaml&lt;/code&gt; files becomes crucial. The &lt;strong&gt;&lt;a href="https://github.com/dadav/helm-schema" rel="noopener noreferrer"&gt;Helm Schema plugin&lt;/a&gt;&lt;/strong&gt; uses &lt;strong&gt;JSON Schema&lt;/strong&gt; to validate your Helm chart values. This helps ensure that the values provided by end users conform to the expected data types, structures, and constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation &amp;amp; Usage
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Install the plugin&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   helm plugin &lt;span class="nb"&gt;install &lt;/span&gt;https://github.com/dadav/helm-schema
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add a &lt;code&gt;values.schema.json&lt;/code&gt;&lt;/strong&gt;:
In your chart’s root, create a file named &lt;code&gt;values.schema.json&lt;/code&gt;. This file defines the schema of your values. For example:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&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;"$schema"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://json-schema.org/draft-07/schema#"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"replicaCount"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"integer"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"minimum"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&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;"image"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"object"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
         &lt;/span&gt;&lt;span class="nl"&gt;"properties"&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;"repository"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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;"tag"&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;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"string"&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="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;"repository"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
     &lt;/span&gt;&lt;span class="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;"replicaCount"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"image"&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;ol&gt;
&lt;li&gt;
&lt;strong&gt;Validate your values&lt;/strong&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   helm schema my-awesome-chart/values.schema.json &lt;span class="nt"&gt;-f&lt;/span&gt; values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will check if &lt;code&gt;values.yaml&lt;/code&gt; meets the constraints defined in &lt;code&gt;values.schema.json&lt;/code&gt;. If something is off (e.g., missing required keys, using a string instead of an integer), it will throw a validation error.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Using Helm Schema
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Early Catch&lt;/strong&gt; 🐞: Validate charts before deploying to production, catching misconfiguration early.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear Documentation&lt;/strong&gt; 💡: The JSON schema itself acts as documentation, helping end users understand required and optional fields.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Better Collaboration&lt;/strong&gt; 🤝: With the schema enforced, teams can rest assured that values used in different environments adhere to the same rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Including Tests in Your Helm Charts ✅
&lt;/h2&gt;

&lt;p&gt;Testing is critical to verify that your Kubernetes resources deploy and function as expected. Helm offers a straightforward way to define and run tests within your chart. These tests are essentially Kubernetes jobs/pods that run checks to ensure your application is responding as intended.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Helm Tests Work
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create a Test File&lt;/strong&gt;: In your chart’s &lt;code&gt;templates/tests/&lt;/code&gt; directory, create a file (e.g., &lt;code&gt;test-connection.yaml&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add Test Annotations&lt;/strong&gt;: Include special annotations recognized by Helm:
&lt;/li&gt;
&lt;/ol&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;Pod&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;include&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"my-awesome-chart.fullname" . }}-test-connection"&lt;/span&gt;
     &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- include "my-awesome-chart.labels" . | nindent 4&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
     &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;helm.sh/hook"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test&lt;/span&gt;
       &lt;span class="s"&gt;"helm.sh/hook-delete-policy"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;before-hook-creation&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;test-connection&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;busybox&lt;/span&gt;
         &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sh'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-c'&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;echo&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Running&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;connection&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;test...";&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sleep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
     &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Never&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;helm.sh/hook&lt;/code&gt;: &lt;code&gt;test&lt;/code&gt;&lt;/strong&gt; tells Helm that this Pod is used for testing.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;helm.sh/hook-delete-policy&lt;/code&gt;: &lt;code&gt;before-hook-creation&lt;/code&gt;&lt;/strong&gt; cleans up old test pods before creating new ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run the Tests&lt;/strong&gt;:
After installing or upgrading your chart, simply run:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   helm &lt;span class="nb"&gt;test &lt;/span&gt;my-awesome-chart-release
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Helm will execute the test pods, and if they all pass (i.e., containers exit with code &lt;code&gt;0&lt;/code&gt;), your chart tests succeed! 🎉&lt;/p&gt;

&lt;h3&gt;
  
  
  Writing Meaningful Tests
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Functionality Tests&lt;/strong&gt;: Verify that your application can handle basic requests (e.g., &lt;code&gt;curl&lt;/code&gt; the service endpoint).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readiness Tests&lt;/strong&gt;: Check your application’s endpoints for readiness or health endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Configuration Tests&lt;/strong&gt;: If your chart has multiple configuration options, test them in ephemeral environments to ensure no regressions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Auto-Generating Documentation with helm-docs 📝
&lt;/h2&gt;

&lt;p&gt;Maintaining up-to-date documentation can be tricky as your chart evolves. That’s where &lt;strong&gt;&lt;a href="https://github.com/norwoodj/helm-docs" rel="noopener noreferrer"&gt;helm-docs&lt;/a&gt;&lt;/strong&gt; comes in. It automatically generates documentation for your Helm chart by reading metadata from your chart files (like &lt;code&gt;Chart.yaml&lt;/code&gt; and &lt;code&gt;values.yaml&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing helm-docs
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;brew &lt;span class="nb"&gt;install &lt;/span&gt;norwoodj/tap/helm-docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(See the &lt;a href="https://github.com/norwoodj/helm-docs" rel="noopener noreferrer"&gt;helm-docs repo&lt;/a&gt; for alternative installation methods.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Using helm-docs
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add a &lt;code&gt;README.md.gotmpl&lt;/code&gt;&lt;/strong&gt;
Create a template for your documentation—usually named something like &lt;code&gt;README.md.gotmpl&lt;/code&gt;. You can define sections for chart metadata, usage, and parameters. For example:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   # {{ .Name }}

   {{ .Description }}

   ## Parameters
   | Name | Description | Value |
   ||-|-|
   {{- range .Values }}
   | {{ .Path }} | {{ .Description }} | {{ .Default | quote }} |
   {{- end }}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Run helm-docs&lt;/strong&gt;
Navigate to your chart directory and run:
&lt;/li&gt;
&lt;/ol&gt;

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

&lt;/div&gt;



&lt;p&gt;This command will parse your &lt;code&gt;values.yaml&lt;/code&gt;, &lt;code&gt;Chart.yaml&lt;/code&gt;, and any comments or metadata to generate a &lt;code&gt;README.md&lt;/code&gt; file based on your template.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commit &amp;amp; Share&lt;/strong&gt;
Once &lt;code&gt;README.md&lt;/code&gt; is generated, commit it to your repository. Now, anyone viewing your chart can see the current parameters, default values, and usage instructions at a glance.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Benefits of helm-docs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automatic Updates&lt;/strong&gt; ⏰: Whenever you update &lt;code&gt;values.yaml&lt;/code&gt; or &lt;code&gt;Chart.yaml&lt;/code&gt;, re-running &lt;code&gt;helm-docs&lt;/code&gt; keeps your README in sync.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consistent Format&lt;/strong&gt; 📄: Enforces a uniform structure across multiple charts in your organization.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time Savings&lt;/strong&gt; ⏲️: Spend less time manually editing documentation and more time improving your charts!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Wrapping It Up 🎁
&lt;/h2&gt;

&lt;p&gt;By following these guidelines:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Structure&lt;/strong&gt; your charts consistently.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage&lt;/strong&gt; the Helm Schema plugin to validate configurations using JSON Schema.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include&lt;/strong&gt; tests to confirm your chart works as intended.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-generate&lt;/strong&gt; documentation with helm-docs to keep docs current and accurate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;…you’ll have robust, maintainable, and user-friendly Helm charts. Whether you’re deploying a tiny microservice or a huge enterprise application, these best practices will help you ship fast and confidently. ⚡&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://helm.sh/docs/" rel="noopener noreferrer"&gt;Helm Official Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/dadav/helm-schema" rel="noopener noreferrer"&gt;Helm Schema Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/norwoodj/helm-docs" rel="noopener noreferrer"&gt;helm-docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Further Reading:&lt;/strong&gt; &lt;a href="https://dev.to/hkhelil/ensuring-effective-helm-charts-with-linting-testing-and-diff-checks-ni0"&gt;Ensuring Effective Helm Charts with Linting, Testing, and Diff Checks&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have any questions or tips, feel free to share them in the comments below. Together, we can make Helm charts smoother to write, more reliable to deploy, and easier to maintain! 🥳&lt;/p&gt;

&lt;p&gt;Happy Helming and clustering ! 🐳&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Unlocking Secrets with External Secrets Operator 🔐✨</title>
      <dc:creator>Hamdi (KHELIL) LION</dc:creator>
      <pubDate>Fri, 03 Jan 2025 20:00:01 +0000</pubDate>
      <link>https://forem.com/hkhelil/unlocking-secrets-with-external-secrets-operator-2f89</link>
      <guid>https://forem.com/hkhelil/unlocking-secrets-with-external-secrets-operator-2f89</guid>
      <description>&lt;p&gt;In modern cloud-native applications, securely managing sensitive data like API keys, database credentials, and certificates is a top priority. Two powerful tools stand out when integrating secrets into Kubernetes: &lt;strong&gt;External Secrets Operator&lt;/strong&gt; and &lt;strong&gt;SecretStoreProviders plugin&lt;/strong&gt; (for Azure and AWS). Let’s dive into how to use them, their differences, and when to pick one over the other. 🚀&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;What Is External Secrets Operator?&lt;/strong&gt; 🤔
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;External Secrets Operator&lt;/strong&gt; (ESO) simplifies secret management in Kubernetes by integrating external secret stores directly into your cluster. Instead of manually creating Kubernetes Secrets, ESO syncs secrets from providers like AWS Secrets Manager, Azure Key Vault, or HashiCorp Vault.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Why Use External Secrets Operator?&lt;/strong&gt; 🌟
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Centralized Secret Management:&lt;/strong&gt; Manage secrets in your provider of choice while syncing them to Kubernetes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation:&lt;/strong&gt; No need to manually update Kubernetes Secrets when values change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security:&lt;/strong&gt; Leverages providers' robust access control and encryption mechanisms.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to Use External Secrets Operator&lt;/strong&gt; 🛠️
&lt;/h2&gt;

&lt;p&gt;Here’s a quick guide to setting up ESO:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Install External Secrets Operator&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Install ESO via Helm:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add external-secrets https://charts.external-secrets.io
helm repo update
helm &lt;span class="nb"&gt;install &lt;/span&gt;external-secrets external-secrets/external-secrets
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 2: Configure a SecretStore&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Create a &lt;code&gt;SecretStore&lt;/code&gt; resource to connect to your external provider. For example, if you're using AWS Secrets Manager:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&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;SecretStore&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;aws-secret-store&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;aws&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretsManager&lt;/span&gt;
      &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-west-2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For Azure Key Vault:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&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;SecretStore&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;azure-secret-store&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;azurekv&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;tenantId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;your-tenant-id&amp;gt;&lt;/span&gt;
      &lt;span class="na"&gt;vaultUrl&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://&amp;lt;your-vault-name&amp;gt;.vault.azure.net/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 3: Define an ExternalSecret&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Bind your external secret to a Kubernetes Secret:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&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;ExternalSecret&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;my-app-secret&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;secretStoreRef&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;aws-secret-store&lt;/span&gt;
  &lt;span class="na"&gt;target&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;my-k8s-secret&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apiKey&lt;/span&gt;
      &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/my-app/api-key&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example pulls the &lt;code&gt;/my-app/api-key&lt;/code&gt; from AWS Secrets Manager and creates a Kubernetes Secret named &lt;code&gt;my-k8s-secret&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For templating values:&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;external-secrets.io/v1beta1&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;ExternalSecret&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;my-template-secret&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;secretStoreRef&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;azure-secret-store&lt;/span&gt;
  &lt;span class="na"&gt;target&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;templated-secret&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;secretKey&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;connectionString&lt;/span&gt;
      &lt;span class="na"&gt;remoteRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/database/connection-string&lt;/span&gt;
  &lt;span class="na"&gt;dataFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;extract&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;key-values&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;strong&gt;How Does It Compare to SecretStoreProviders Plugin?&lt;/strong&gt; ⚖️
&lt;/h2&gt;

&lt;p&gt;Both ESO and the SecretStoreProviders plugin enable secure secret management in Kubernetes. Here’s how they stack up:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;1. Scope and Features&lt;/strong&gt; 🌍
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;External Secrets Operator&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Supports multiple providers (AWS, Azure, GCP, HashiCorp Vault, etc.).&lt;/li&gt;
&lt;li&gt;Extensive customization and features like templating.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;SecretStoreProviders Plugin&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Focused primarily on Azure Key Vault and AWS Secrets Manager.&lt;/li&gt;
&lt;li&gt;Limited to CSI driver-based integration.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;2. Installation and Complexity&lt;/strong&gt; 🤹
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ESO&lt;/strong&gt;: Requires deploying the operator and defining &lt;code&gt;SecretStore&lt;/code&gt; and &lt;code&gt;ExternalSecret&lt;/code&gt; resources. It’s slightly more complex but offers more flexibility.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SecretStoreProviders&lt;/strong&gt;: Simpler setup with a CSI driver. However, its capabilities are narrower.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;3. Performance and Scalability&lt;/strong&gt; 🚀
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ESO&lt;/strong&gt;: Handles more complex scenarios but may introduce slight overhead due to the operator’s reconciliation process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SecretStoreProviders&lt;/strong&gt;: Lightweight but less feature-rich, making it better for straightforward use cases.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;4. Provider Support&lt;/strong&gt; 🌐
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ESO&lt;/strong&gt;: Supports a broad range of providers beyond AWS and Azure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SecretStoreProviders&lt;/strong&gt;: Limited to Azure Key Vault and AWS Secrets Manager.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;5. Example Use Cases&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ESO&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Sync multiple secrets from AWS Secrets Manager and Azure Key Vault into a single Kubernetes namespace.&lt;/li&gt;
&lt;li&gt;Templating and merging secrets from different sources into one Secret.&lt;/li&gt;
&lt;li&gt;Using annotations to dynamically manage secret lifecycles.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;SecretStoreProviders&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;Directly mount Azure Key Vault secrets as files in a pod.&lt;/li&gt;
&lt;li&gt;Lightweight secret access for apps needing minimal Kubernetes API interaction.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;How to Use SecretStoreProviders Plugin for Azure and AWS&lt;/strong&gt; 🔧
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Azure Key Vault Example&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the Azure Key Vault CSI driver:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/Azure/secrets-store-csi-driver-provider-azure/main/deployment/secretproviderclass.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;SecretProviderClass&lt;/code&gt; to define the secrets you want to access:&lt;br&gt;
&lt;/p&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;secrets-store.csi.x-k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretProviderClass&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;azure-keyvault&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;azure&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;usePodIdentity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;false"&lt;/span&gt;
    &lt;span class="na"&gt;clientID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-client-id&amp;gt;"&lt;/span&gt;
    &lt;span class="na"&gt;tenantID&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-tenant-id&amp;gt;"&lt;/span&gt;
    &lt;span class="na"&gt;keyvaultName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;your-keyvault-name&amp;gt;"&lt;/span&gt;
    &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;array:&lt;/span&gt;
        &lt;span class="s"&gt;- |&lt;/span&gt;
        &lt;span class="s"&gt;objectName: secret1&lt;/span&gt;
        &lt;span class="s"&gt;objectType: secret&lt;/span&gt;
        &lt;span class="s"&gt;- |&lt;/span&gt;
        &lt;span class="s"&gt;objectName: certificate1&lt;/span&gt;
        &lt;span class="s"&gt;objectType: cert&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mount the secrets in a pod:&lt;br&gt;
&lt;/p&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;Pod&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-secrets-store&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&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;secrets-store-inline&lt;/span&gt;
        &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/secrets-store"&lt;/span&gt;
        &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;secrets-store-inline&lt;/span&gt;
    &lt;span class="na"&gt;csi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets-store.csi.k8s.io&lt;/span&gt;
        &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;volumeAttributes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;secretProviderClass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;azure-keyvault"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;AWS Secrets Manager Example&lt;/strong&gt;
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Install the AWS Secrets Manager CSI driver:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-k&lt;/span&gt; &lt;span class="s2"&gt;"github.com/aws/secrets-store-csi-driver-provider-aws/deployment?ref=main"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a &lt;code&gt;SecretProviderClass&lt;/code&gt; to define the secrets:&lt;br&gt;
&lt;/p&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;secrets-store.csi.x-k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SecretProviderClass&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;aws-secrets&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;provider&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws&lt;/span&gt;
&lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;objects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;- objectName: "mySecret"&lt;/span&gt;
        &lt;span class="s"&gt;objectType: "secretsmanager"&lt;/span&gt;
        &lt;span class="s"&gt;region: "us-west-2"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mount the secrets in a pod:&lt;br&gt;
&lt;/p&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;Pod&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-secrets-store&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&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;secrets-store-inline&lt;/span&gt;
        &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt/secrets-store"&lt;/span&gt;
        &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;secrets-store-inline&lt;/span&gt;
    &lt;span class="na"&gt;csi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secrets-store.csi.k8s.io&lt;/span&gt;
        &lt;span class="na"&gt;readOnly&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;volumeAttributes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;secretProviderClass&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;aws-secrets"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Which One Should You Choose?&lt;/strong&gt; 🤷‍♀️
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Use External Secrets Operator if:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You need to work with multiple providers.&lt;/li&gt;
&lt;li&gt;Your use case involves advanced templating or secret transformation.&lt;/li&gt;
&lt;li&gt;Flexibility and extensibility are priorities.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Use SecretStoreProviders Plugin if:&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You primarily work with Azure or AWS secrets.&lt;/li&gt;
&lt;li&gt;Simplicity and minimal resource usage are key.&lt;/li&gt;
&lt;li&gt;You prefer to use the Kubernetes CSI driver.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion&lt;/strong&gt; 🎉
&lt;/h2&gt;

&lt;p&gt;Both &lt;strong&gt;External Secrets Operator&lt;/strong&gt; and &lt;strong&gt;SecretStoreProviders plugin&lt;/strong&gt; are excellent tools for managing secrets in Kubernetes. Your choice depends on your use case, complexity, and cloud provider preferences. With either option, you’ll be well-equipped to handle secrets securely in your Kubernetes clusters. 🔒&lt;/p&gt;

&lt;p&gt;Happy Clustering! 🚀&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
