<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Tandap Noel Bansikah</title>
    <description>The latest articles on DEV Community by Tandap Noel Bansikah (@bansikah).</description>
    <link>https://dev.to/bansikah</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F986646%2F61247089-46d8-4739-be45-f47d34d83d9f.jpeg</url>
      <title>DEV Community: Tandap Noel Bansikah</title>
      <link>https://dev.to/bansikah</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bansikah"/>
    <language>en</language>
    <item>
      <title>Kubernetes Shared Volumes: A Comprehensive Guide</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Sat, 22 Aug 2026 21:45:49 +0000</pubDate>
      <link>https://dev.to/bansikah/kubernetes-shared-volumes-a-comprehensive-guide-1e05</link>
      <guid>https://dev.to/bansikah/kubernetes-shared-volumes-a-comprehensive-guide-1e05</guid>
      <description>&lt;h1&gt;
  
  
  Kubernetes Shared Volumes: A Comprehensive Guide
&lt;/h1&gt;

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

&lt;ol&gt;
&lt;li&gt;Prerequisites&lt;/li&gt;
&lt;li&gt;Introduction&lt;/li&gt;
&lt;li&gt;Core Concepts&lt;/li&gt;
&lt;li&gt;Volume Types&lt;/li&gt;
&lt;li&gt;Access Modes Explained&lt;/li&gt;
&lt;li&gt;Hands-On Examples&lt;/li&gt;
&lt;li&gt;Best Practices&lt;/li&gt;
&lt;li&gt;Troubleshooting&lt;/li&gt;
&lt;li&gt;Summary and Next Steps&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Shared volumes in Kubernetes represent a fundamental storage mechanism that enables containers and pods to exchange data in a coordinated manner. This capability is essential for building distributed applications where multiple components need to access shared resources or coordinate asynchronously.&lt;/p&gt;

&lt;p&gt;This comprehensive guide covers the conceptual foundations of Kubernetes volumes, explores different volume types, and provides detailed hands-on examples. Whether you are managing temporary data within a single pod or orchestrating communication between multiple microservices, understanding volumes is crucial for effective Kubernetes deployments.&lt;/p&gt;

&lt;p&gt;For the official Kubernetes documentation on volumes, refer to the &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/" rel="noopener noreferrer"&gt;Kubernetes Volumes documentation&lt;/a&gt;.&lt;/p&gt;




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

&lt;p&gt;Before working through the hands-on examples in this guide, ensure you have the following tools installed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;kind&lt;/strong&gt;: A tool for running local Kubernetes clusters using Docker containers. Kind simplifies learning and testing Kubernetes concepts without requiring a full cluster setup.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download and installation instructions: &lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/" rel="noopener noreferrer"&gt;kind Getting Started Guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Verify installation: &lt;code&gt;kind version&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;kubectl&lt;/strong&gt;: The Kubernetes command-line tool for interacting with Kubernetes clusters&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download and installation instructions: &lt;a href="https://kubernetes.io/docs/tasks/tools/" rel="noopener noreferrer"&gt;Kubernetes kubectl Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Verify installation: &lt;code&gt;kubectl version --client&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Docker&lt;/strong&gt;: Required by kind to create containerized Kubernetes cluster nodes&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Download and installation instructions: &lt;a href="https://www.docker.com/products/docker-desktop" rel="noopener noreferrer"&gt;Docker Desktop Installation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Verify installation: &lt;code&gt;docker --version&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Quick Start with kind
&lt;/h3&gt;

&lt;p&gt;Create a local Kubernetes cluster for practicing these examples:&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="c"&gt;# Create a new kind cluster&lt;/span&gt;
kind create cluster &lt;span class="nt"&gt;--name&lt;/span&gt; volumes-demo

&lt;span class="c"&gt;# Verify cluster is running&lt;/span&gt;
kubectl cluster-info &lt;span class="nt"&gt;--context&lt;/span&gt; kind-volumes-demo

&lt;span class="c"&gt;# Switch to the new cluster context&lt;/span&gt;
kubectl config use-context kind-volumes-demo

&lt;span class="c"&gt;# When finished, delete the cluster&lt;/span&gt;
kind delete cluster &lt;span class="nt"&gt;--name&lt;/span&gt; volumes-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Core Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is a Volume?
&lt;/h3&gt;

&lt;p&gt;A volume is a directory, possibly with data in it, which is accessible to the containers in a pod. It is a way to mount storage into a container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pod Lifecycle vs Volume Lifecycle
&lt;/h3&gt;

&lt;p&gt;Understanding the relationship between pod and volume lifecycles is critical when designing storage strategies. When a pod is terminated, ephemeral volumes are also deleted. However, persistent volumes survive pod deletion and can be remounted to new pods, ensuring data durability.&lt;/p&gt;

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

&lt;p&gt;This distinction determines which volume type is appropriate for your use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;emptyDir volumes&lt;/strong&gt; are created when a pod starts and deleted when the pod terminates. These are suitable for temporary data that does not need to persist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PersistentVolumes&lt;/strong&gt; exist independently of any pod. Data written to persistent storage remains available even after pod deletion, making them ideal for production environments requiring data durability.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Share Volumes?
&lt;/h3&gt;

&lt;p&gt;In distributed systems, components frequently need to exchange data or coordinate their activities. Consider a data processing pipeline with multiple stages:&lt;/p&gt;

&lt;p&gt;A producer service generates data and writes it to a shared location. A consumer service monitors this location, processes incoming data, and generates results. A separate cleanup service archives processed files according to retention policies.&lt;/p&gt;

&lt;p&gt;Without shared volumes, these independent pods would remain isolated with no mechanism for communication. By leveraging shared volumes, these components coordinate asynchronously through filesystem operations, creating a loosely coupled yet efficient distributed system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Volume Types
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;emptyDir&lt;/strong&gt; - Temporary Shared Storage
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What It Does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Creates an empty directory when a pod starts&lt;/li&gt;
&lt;li&gt;Automatically deleted when the pod terminates&lt;/li&gt;
&lt;li&gt;Shared among all containers in the same pod&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Use Cases
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Temporary scratch space&lt;/li&gt;
&lt;li&gt;Sharing data between sidecar containers&lt;/li&gt;
&lt;li&gt;Cache storage&lt;/li&gt;
&lt;li&gt;Logs that don't need to persist&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Key Characteristics
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;temp-storage&lt;/span&gt;
    &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Cannot be shared across multiple pods&lt;/li&gt;
&lt;li&gt;Data is lost when the pod is deleted&lt;/li&gt;
&lt;li&gt;Offers low-latency access without storage provisioning overhead&lt;/li&gt;
&lt;li&gt;Suitable for non-critical temporary data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For more details on emptyDir volumes, see the &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/#emptydir" rel="noopener noreferrer"&gt;official Kubernetes emptyDir documentation&lt;/a&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. &lt;strong&gt;PersistentVolumeClaim (PVC)&lt;/strong&gt; - Durable Shared Storage
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What It Does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Requests persistent storage from the cluster&lt;/li&gt;
&lt;li&gt;Storage survives pod deletion and recreation&lt;/li&gt;
&lt;li&gt;Can be shared across multiple pods (with proper access modes)&lt;/li&gt;
&lt;li&gt;Decouples storage provisioning from pod lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Use Cases
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Production databases&lt;/li&gt;
&lt;li&gt;Shared data for multiple pods&lt;/li&gt;
&lt;li&gt;Long-term data retention&lt;/li&gt;
&lt;li&gt;Asynchronous application communication&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Key Characteristics
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;persistent-storage&lt;/span&gt;
    &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-pvc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Data persists beyond the pod lifecycle, surviving pod deletions and recreations&lt;/li&gt;
&lt;li&gt;Enables data sharing across multiple pods when configured with appropriate access modes&lt;/li&gt;
&lt;li&gt;Suitable for production workloads requiring data durability&lt;/li&gt;
&lt;li&gt;Supports multiple storage backends including NFS, cloud block storage, and distributed filesystems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For comprehensive details on PersistentVolumeClaims, consult the &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/" rel="noopener noreferrer"&gt;official PVC documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;hostPath&lt;/strong&gt; - Node Local Storage
&lt;/h3&gt;

&lt;h4&gt;
  
  
  What It Does
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Mounts a path from the host node into the pod&lt;/li&gt;
&lt;li&gt;Useful for accessing host-level resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Use Cases
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Docker socket access (for Docker-in-Docker)&lt;/li&gt;
&lt;li&gt;Host logs monitoring&lt;/li&gt;
&lt;li&gt;Node-level debugging&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Considerations
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Not portable across nodes; volumes are tied to the host where they are mounted&lt;/li&gt;
&lt;li&gt;Introduces security considerations as pods gain direct access to host filesystem paths&lt;/li&gt;
&lt;li&gt;Not recommended for multi-node clusters as data may not be accessible from other nodes&lt;/li&gt;
&lt;li&gt;Best reserved for debugging and specialized use cases requiring direct host access&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Access Modes Explained
&lt;/h2&gt;

&lt;p&gt;Access modes define how a PersistentVolume can be accessed and are critical when designing multi-pod storage solutions. Different access modes support different deployment scenarios and determine whether multiple pods can simultaneously access the same volume.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Access Mode&lt;/th&gt;
&lt;th&gt;Abbreviation&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Multi-Pod Support&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReadWriteOnce&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RWO&lt;/td&gt;
&lt;td&gt;Single pod can read and write&lt;/td&gt;
&lt;td&gt;Not applicable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReadOnlyMany&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;ROX&lt;/td&gt;
&lt;td&gt;Multiple pods can read; no write access&lt;/td&gt;
&lt;td&gt;Yes (read-only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReadWriteMany&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RWX&lt;/td&gt;
&lt;td&gt;Multiple pods can read and write&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For detailed information on access modes, refer to the &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes" rel="noopener noreferrer"&gt;Kubernetes access modes documentation&lt;/a&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Selecting the Appropriate Access Mode
&lt;/h3&gt;

&lt;p&gt;The access mode you choose depends on your deployment pattern. For single-pod deployments where only one instance needs access:&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;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;  &lt;span class="c1"&gt;# Appropriate for single-pod deployments&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When multiple independent pods must simultaneously write to and read from the same storage:&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;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteMany&lt;/span&gt;  &lt;span class="c1"&gt;# Required for multi-pod write scenarios&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Hands-On Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example 1: Shared Volume Within a Single Pod (emptyDir)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Two containers in the same pod need to share data. A "writer" container creates a file, and a "reader" container reads it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Create the Pod Manifest
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a file: shared-volume-pod.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; shared-volume-pod.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: Pod
metadata:
  name: shared-demo
spec:
  containers:
  - name: writer
    image: busybox:latest
    command:
      - sh
      - -c
      - |
        echo "Hello from writer container" &amp;gt; /shared/msg.txt
        sleep 3600  # Keep container running
    volumeMounts:
    - name: shared-storage
      mountPath: /shared

  - name: reader
    image: busybox:latest
    command:
      - sh
      - -c
      - |
        echo "Waiting for message..."
        while [ ! -f /shared/msg.txt ]; do
          echo "File not ready, waiting..."
          sleep 2
        done
        echo "Message received:"
        cat /shared/msg.txt
        sleep 3600  # Keep container running
    volumeMounts:
    - name: shared-storage
      mountPath: /shared

  volumes:
  - name: shared-storage
    emptyDir: {}
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Deploy the Pod
&lt;/h4&gt;



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

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Verify Communication
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check pod status&lt;/span&gt;
kubectl get pod shared-demo

&lt;span class="c"&gt;# View logs from writer container&lt;/span&gt;
kubectl logs shared-demo &lt;span class="nt"&gt;-c&lt;/span&gt; writer

&lt;span class="c"&gt;# View logs from reader container&lt;/span&gt;
kubectl logs shared-demo &lt;span class="nt"&gt;-c&lt;/span&gt; reader

&lt;span class="c"&gt;# Verify file exists in shared volume&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;shared-demo &lt;span class="nt"&gt;-c&lt;/span&gt; reader &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /shared/msg.txt

&lt;span class="c"&gt;# Verify from writer's perspective&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;shared-demo &lt;span class="nt"&gt;-c&lt;/span&gt; writer &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /shared/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Expected Output
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reader logs:
  Waiting for message...
  Message received:
  Hello from writer container
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  How It Works
&lt;/h4&gt;

&lt;p&gt;This example demonstrates intra-pod volume sharing. The pod contains two containers, both mounting the same emptyDir volume at the path &lt;code&gt;/shared&lt;/code&gt;. When the writer container creates a file at &lt;code&gt;/shared/msg.txt&lt;/code&gt;, the reader container can immediately access it through the same mount path.&lt;/p&gt;

&lt;p&gt;The volume acts as a shared filesystem namespace, allowing both containers to communicate through file operations without requiring network-based communication protocols. This mechanism is particularly useful for tightly coupled containers that need to share state or coordinate work within a single pod.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Cleanup
&lt;/h4&gt;



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

&lt;/div&gt;






&lt;h3&gt;
  
  
  Example 2: Single Pod with PersistentVolumeClaim (Data Persistence)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: Your application needs to store data that survives pod restarts. This is the pattern used in production systems.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Create the PersistentVolumeClaim
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File: pvc.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pvc.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: app-storage-pvc
spec:
  accessModes:
    - ReadWriteOnce  # Only one pod, so RWO is sufficient
  resources:
    requests:
      storage: 1Gi  # Request 1 gigabyte
  # Note: storageClassName may need to match your cluster's configuration
  # For Docker Desktop: use 'docker-desktop' or 'standard'
  # For Minikube: use 'standard'
  storageClassName: standard
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Create the Pod Using the PVC
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File: pvc-pod.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pvc-pod.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: Pod
metadata:
  name: persistent-writer
spec:
  containers:
  - name: writer
    image: busybox:latest
    command:
      - sh
      - -c
      - |
        echo "Data written at: &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt;&amp;gt; /data/persistent-data.txt
        cat /data/persistent-data.txt
        sleep 3600
    volumeMounts:
    - name: persistent-storage
      mountPath: /data

  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: app-storage-pvc
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Deploy
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the PVC first&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pvc.yml

&lt;span class="c"&gt;# Wait for PVC to be bound&lt;/span&gt;
kubectl get pvc

&lt;span class="c"&gt;# Then create the pod&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pvc-pod.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Verify Data Persistence
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Check the content written&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;persistent-writer &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /data/persistent-data.txt

&lt;span class="c"&gt;# Delete the pod&lt;/span&gt;
kubectl delete pod persistent-writer

&lt;span class="c"&gt;# Re-create the pod (use same manifest)&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pvc-pod.yml

&lt;span class="c"&gt;# Check that the data still exists!&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;persistent-writer &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /data/persistent-data.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Expected Behavior
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First run output:
  Data written at: Sun Aug 17 10:30:45 UTC 2026

After pod deletion and recreation:
  Data written at: Sun Aug 17 10:30:45 UTC 2026
  Data written at: Sun Aug 17 10:35:22 UTC 2026
  (Original data persists; new data appended)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Data Durability and Pod Recreation
&lt;/h4&gt;

&lt;p&gt;A key advantage of PersistentVolumeClaims is data durability across pod lifecycle events. When a pod is deleted, the persistent volume remains intact with all data preserved. When a new pod is created with the same PVC specification, it automatically mounts the existing persistent volume and regains access to all previously written data.&lt;/p&gt;

&lt;p&gt;This behavior is fundamental to Kubernetes' design for stateful applications, enabling pod replacement and recovery without data loss.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Example 3: Multiple Pods Sharing a PersistentVolume (ReadWriteMany)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Scenario&lt;/strong&gt;: This is the most powerful use case. Multiple independent pods communicate through shared storage—the foundation of distributed systems and microservices.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Create a Shared PVC (ReadWriteMany)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File: shared-pvc.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; shared-pvc.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: shared-data-pvc
spec:
  accessModes:
    - ReadWriteMany  # Required for multiple pods to write concurrently
  resources:
    requests:
      storage: 2Gi
  storageClassName: standard
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Create Producer Pod (Writer)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File: producer-pod.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; producer-pod.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: Pod
metadata:
  name: data-producer
spec:
  containers:
  - name: producer
    image: busybox:latest
    command:
      - sh
      - -c
      - |
        for i in 1 2 3 4 5; do
          TIMESTAMP=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; &lt;span class="s1"&gt;'+%Y-%m-%d %H:%M:%S'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
          echo "[&lt;/span&gt;&lt;span class="nv"&gt;$TIMESTAMP&lt;/span&gt;&lt;span class="sh"&gt;] Producing record #&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt;&amp;gt; /shared/data.txt
          sleep 2
        done
        echo "Producer finished!"
        sleep 3600
    volumeMounts:
    - name: shared-vol
      mountPath: /shared

  volumes:
  - name: shared-vol
    persistentVolumeClaim:
      claimName: shared-data-pvc
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Create Consumer Pod (Reader)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File: consumer-pod.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; consumer-pod.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: Pod
metadata:
  name: data-consumer
spec:
  containers:
  - name: consumer
    image: busybox:latest
    command:
      - sh
      - -c
      - |
        echo "Consumer waiting for data..."
        sleep 3  # Let producer start
        while true; do
          if [ -f /shared/data.txt ]; then
            echo "=== Data received ==="
            cat /shared/data.txt
            echo "=== End of data ==="
          else
            echo "Waiting for data file..."
          fi
          sleep 5
        done
    volumeMounts:
    - name: shared-vol
      mountPath: /shared

  volumes:
  - name: shared-vol
    persistentVolumeClaim:
      claimName: shared-data-pvc
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Deploy Everything
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Create the shared PVC&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; shared-pvc.yml

&lt;span class="c"&gt;# Wait for PVC to be bound&lt;/span&gt;
kubectl get pvc
&lt;span class="c"&gt;# Expected: shared-data-pvc   Bound   ...&lt;/span&gt;

&lt;span class="c"&gt;# Deploy both pods&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; producer-pod.yml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; consumer-pod.yml

&lt;span class="c"&gt;# Verify both are running&lt;/span&gt;
kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5: Monitor Communication
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1: Watch producer logs&lt;/span&gt;
kubectl logs &lt;span class="nt"&gt;-f&lt;/span&gt; data-producer

&lt;span class="c"&gt;# Terminal 2: Watch consumer logs&lt;/span&gt;
kubectl logs &lt;span class="nt"&gt;-f&lt;/span&gt; data-consumer

&lt;span class="c"&gt;# Terminal 3: Check the shared file in real-time&lt;/span&gt;
watch kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;data-consumer &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /shared/data.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Expected Flow
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Time: 0s
  Producer: Creates /shared/data.txt
  Consumer: Waiting...

Time: 2s
  Producer: Writes first record
  Consumer: Sees file, reads content

Time: 4s
  Producer: Writes second record
  Consumer: Reads updated content

... and so on
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Architecture and Communication Pattern
&lt;/h4&gt;

&lt;p&gt;This multi-pod scenario illustrates the decoupled communication pattern that Kubernetes shared volumes enable. Two independent pods, the producer and consumer, operate autonomously without direct knowledge of each other.&lt;/p&gt;

&lt;p&gt;The producer pod periodically writes data records to the shared volume. The consumer pod independently monitors the shared location, reads available data, processes it, and writes results. Neither pod requires awareness of the other's existence or operational state.&lt;/p&gt;

&lt;p&gt;This asynchronous, filesystem-based communication pattern decouples component lifecycle management from data coordination, enabling resilient distributed systems. If the consumer pod restarts, the producer continues writing data uninterrupted. Similarly, producer failures do not affect the consumer's ability to process previously written data.&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Cleanup
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete pod data-producer data-consumer
kubectl delete pvc shared-data-pvc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  Example 4: Real-World Scenario - Data Pipeline (Like the Project)
&lt;/h3&gt;

&lt;p&gt;This mirrors the actual pipeline in your project.&lt;/p&gt;

&lt;h4&gt;
  
  
  Step 1: Create Namespace
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace data-pipeline
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 2: Create Shared Storage
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File: pipeline-pvc.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; pipeline-pvc.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  namespace: data-pipeline
  name: pipeline-shared-storage
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 5Gi
  storageClassName: standard
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 3: Create Scraper Pod (Produces Data)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File: scraper-pod.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; scraper-pod.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: Pod
metadata:
  namespace: data-pipeline
  name: json-scraper
spec:
  containers:
  - name: scraper
    image: busybox:latest
    command:
      - sh
      - -c
      - |
        mkdir -p /data/incoming /data/processed /data/failed

        # Simulate scraping
        echo '{"id": 1, "name": "Product A", "price": 29.99}' &amp;gt; /data/incoming/products-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt; +%Y-%m-%d&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;.json
        echo "[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;] Scraper: Generated JSON file" &amp;gt;&amp;gt; /data/logs/activity.log
        sleep 3600
    volumeMounts:
    - name: shared
      mountPath: /data
  volumes:
  - name: shared
    persistentVolumeClaim:
      claimName: pipeline-shared-storage
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 4: Create Consumer Pod (Processes Data)
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# File: consumer-pod.yml&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; consumer-pod.yml &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
apiVersion: v1
kind: Pod
metadata:
  namespace: data-pipeline
  name: json-consumer
spec:
  containers:
  - name: consumer
    image: busybox:latest
    command:
      - sh
      - -c
      - |
        mkdir -p /data/incoming /data/processed /data/reports

        echo "[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;] Consumer: Started watching for files" &amp;gt;&amp;gt; /data/logs/activity.log

        while true; do
          if [ -f /data/incoming/products-*.json ]; then
            FILE=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; /data/incoming/products-&lt;span class="k"&gt;*&lt;/span&gt;.json 2&amp;gt;/dev/null | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
            if [ -n "&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="sh"&gt;" ]; then
              echo "[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;] Consumer: Processing &lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt;&amp;gt; /data/logs/activity.log

              # Process file
              cat "&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="sh"&gt;" &amp;gt; /data/reports/report-&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;basename&lt;/span&gt; &lt;span class="nv"&gt;$FILE&lt;/span&gt; .json&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;-report.json

              # Move to processed
              mv "&lt;/span&gt;&lt;span class="nv"&gt;$FILE&lt;/span&gt;&lt;span class="sh"&gt;" /data/processed/
              echo "[&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;] Consumer: Completed processing" &amp;gt;&amp;gt; /data/logs/activity.log
            fi
          fi
          sleep 5
        done
    volumeMounts:
    - name: shared
      mountPath: /data
  volumes:
  - name: shared
    persistentVolumeClaim:
      claimName: pipeline-shared-storage
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Step 5: Deploy and Monitor
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Deploy everything&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; pipeline-pvc.yml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; scraper-pod.yml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; consumer-pod.yml

&lt;span class="c"&gt;# Watch the pipeline in action&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; data-pipeline logs &lt;span class="nt"&gt;-f&lt;/span&gt; json-consumer

&lt;span class="c"&gt;# Verify files are being processed&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; data-pipeline &lt;span class="nb"&gt;exec &lt;/span&gt;json-consumer &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /data/incoming/
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; data-pipeline &lt;span class="nb"&gt;exec &lt;/span&gt;json-consumer &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /data/processed/
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; data-pipeline &lt;span class="nb"&gt;exec &lt;/span&gt;json-consumer &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /data/reports/

&lt;span class="c"&gt;# Check activity log&lt;/span&gt;
kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; data-pipeline &lt;span class="nb"&gt;exec &lt;/span&gt;json-consumer &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-20&lt;/span&gt; /data/logs/activity.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Data Pipeline Architecture
&lt;/h4&gt;

&lt;p&gt;This example demonstrates a complete data processing pipeline with multiple stages, each coordinating through shared persistent storage:&lt;/p&gt;

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

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

&lt;h3&gt;
  
  
  1. Select the Appropriate Volume Type
&lt;/h3&gt;

&lt;p&gt;Match your storage solution to your requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;emptyDir&lt;/strong&gt;: Use for temporary data that does not need to persist beyond the pod lifecycle&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PersistentVolumeClaim&lt;/strong&gt;: Use when data must survive pod deletion or recreation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PersistentVolumeClaim with ReadWriteMany&lt;/strong&gt;: Use when multiple pods must simultaneously access the same storage&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;hostPath&lt;/strong&gt;: Use sparingly for specialized scenarios requiring direct host filesystem access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most production scenarios, PersistentVolumeClaims provide the appropriate balance of durability, portability, and multi-pod support.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Use PersistentVolumeClaims in Production Environments
&lt;/h3&gt;

&lt;p&gt;Production systems must ensure data durability and recovery capabilities. Using emptyDir volumes in production deployments risks data loss if pods terminate unexpectedly:&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="c1"&gt;# Not recommended for production: ephemeral storage&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;data&lt;/span&gt;
  &lt;span class="na"&gt;emptyDir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;

&lt;span class="c1"&gt;# Recommended for production: persistent storage&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;data&lt;/span&gt;
  &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app-pvc&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PersistentVolumeClaims provide data durability, recovery mechanisms, and cross-pod data access, making them essential for stateful applications in production environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Configure Appropriate Storage Classes
&lt;/h3&gt;

&lt;p&gt;Kubernetes clusters typically provide multiple storage classes with different performance and cost characteristics. Select the storage class that matches your performance and cost requirements:&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="c1"&gt;# Identify available storage classes&lt;/span&gt;
&lt;span class="s"&gt;kubectl get storageclass&lt;/span&gt;

&lt;span class="c1"&gt;# Specify the appropriate class for your workload&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;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fast-ssd&lt;/span&gt;    &lt;span class="c1"&gt;# For high-performance requirements (databases)&lt;/span&gt;
  &lt;span class="c1"&gt;# or&lt;/span&gt;
  &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;standard&lt;/span&gt;    &lt;span class="c1"&gt;# For general-purpose shared storage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Consult your cluster administrator or cloud provider documentation to understand the available storage classes and their performance characteristics.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Use ReadWriteMany Access Mode Judiciously
&lt;/h3&gt;

&lt;p&gt;While ReadWriteMany access modes enable multi-pod storage sharing, they may introduce performance overhead compared to ReadWriteOnce. Only use ReadWriteMany when your deployment genuinely requires multiple pods to simultaneously write to the same storage:&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="c1"&gt;# Use ReadWriteOnce for single-pod deployments&lt;/span&gt;
&lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteOnce&lt;/span&gt;  &lt;span class="c1"&gt;# Better performance for single-pod access&lt;/span&gt;

&lt;span class="c1"&gt;# Use ReadWriteMany only when multiple pods must write&lt;/span&gt;
&lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteMany&lt;/span&gt;  &lt;span class="c1"&gt;# Use only when necessary for multi-pod access&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Performance implications vary depending on the underlying storage backend. Some storage systems handle concurrent writes efficiently, while others may experience performance degradation.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Monitor Volume Utilization and Performance
&lt;/h3&gt;

&lt;p&gt;Implement monitoring to track storage usage and prevent capacity exhaustion:&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="c"&gt;# Check PVC status and bound status&lt;/span&gt;
kubectl get pvc

&lt;span class="c"&gt;# Retrieve detailed PVC information including usage statistics&lt;/span&gt;
kubectl describe pvc my-pvc

&lt;span class="c"&gt;# Monitor filesystem usage from within a container&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;my-pod &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;df&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; /data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Establish alerts for approaching storage capacity limits to enable proactive scaling before pods encounter write failures due to full storage.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Implement Backup and Disaster Recovery Strategies
&lt;/h3&gt;

&lt;p&gt;Production systems must include backup and recovery procedures to protect against data loss:&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="c"&gt;# Backup PVC data from a running container&lt;/span&gt;
kubectl &lt;span class="nb"&gt;cp &lt;/span&gt;namespace/pod:/data ./backup-data
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Document your storage strategy, backup procedures, and recovery processes in your infrastructure-as-code repository. Many organizations use automated backup solutions that work directly with their chosen storage backends (cloud snapshots, distributed filesystem replication, etc.).&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Define Clear Storage Resource Requests
&lt;/h3&gt;

&lt;p&gt;Specify explicit storage resource requests to ensure the Kubernetes scheduler can appropriately provision storage infrastructure:&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="c1"&gt;# Request the minimum storage required for your application&lt;/span&gt;
&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10Gi&lt;/span&gt;  &lt;span class="c1"&gt;# Requested storage allocation&lt;/span&gt;
  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;20Gi&lt;/span&gt;  &lt;span class="c1"&gt;# Maximum storage allowed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Accurate resource requests enable proper cluster capacity planning and prevent scenarios where pods cannot be scheduled due to insufficient storage availability.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Follow Proper Resource Cleanup Procedures
&lt;/h3&gt;

&lt;p&gt;When decommissioning applications, follow the correct sequence to avoid orphaned resources and unintended data retention:&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="c"&gt;# Delete pods that are using the volume&lt;/span&gt;
kubectl delete pod my-pod

&lt;span class="c"&gt;# Then delete the PersistentVolumeClaim&lt;/span&gt;
kubectl delete pvc my-pvc

&lt;span class="c"&gt;# Finally, delete the underlying PersistentVolume if necessary&lt;/span&gt;
kubectl delete pv my-pv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sequence ensures that pods are terminated before storage is deallocated, preventing write operations to deleted storage and ensuring clean resource cleanup.&lt;/p&gt;




&lt;h2&gt;
  
  
  Troubleshooting Common Issues
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PersistentVolumeClaim Remains in Pending State
&lt;/h3&gt;

&lt;p&gt;When a PVC remains pending, it indicates the cluster cannot provision the requested storage. Investigate using:&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="c"&gt;# Retrieve detailed status information and events&lt;/span&gt;
kubectl describe pvc my-pvc

&lt;span class="c"&gt;# Verify required storage class exists&lt;/span&gt;
kubectl get storageclass

&lt;span class="c"&gt;# Check node and cluster resource availability&lt;/span&gt;
kubectl get nodes
kubectl top nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common causes include unavailable storage classes, insufficient cluster resources, or storage backend issues. Review the PVC events and cluster logs for specific error messages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pod Cannot Access Volume
&lt;/h3&gt;

&lt;p&gt;If a pod cannot access a mounted volume, verify the mount configuration and permissions:&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="c"&gt;# Confirm the mount point exists within the container&lt;/span&gt;
kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;my-pod &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt; /data

&lt;span class="c"&gt;# Review detailed volume mount configuration&lt;/span&gt;
kubectl describe pod my-pod | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-A&lt;/span&gt; 5 Mounts

&lt;span class="c"&gt;# Verify the PVC is in Bound status&lt;/span&gt;
kubectl get pvc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ensure the PVC is in Bound state and the container has appropriate permissions for the mounted path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Data Not Accessible Across Pods
&lt;/h3&gt;

&lt;p&gt;When data written by one pod is not visible to another pod, verify the access mode and PVC configuration:&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="c"&gt;# Confirm both pods reference the same PVC&lt;/span&gt;
kubectl describe pod pod1 | &lt;span class="nb"&gt;grep &lt;/span&gt;persistentVolumeClaim
kubectl describe pod pod2 | &lt;span class="nb"&gt;grep &lt;/span&gt;persistentVolumeClaim

&lt;span class="c"&gt;# Verify access mode supports multi-pod access&lt;/span&gt;
kubectl get pvc &lt;span class="nt"&gt;-o&lt;/span&gt; yaml | &lt;span class="nb"&gt;grep &lt;/span&gt;accessModes
&lt;span class="c"&gt;# Should show: - ReadWriteMany (for multi-pod write scenarios)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Common issues include using ReadWriteOnce access mode for multi-pod deployments, or pods writing to different directories within the same volume.&lt;/p&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Shared volumes in Kubernetes enable a fundamental architectural pattern: decoupled, asynchronous component communication through filesystem-based data exchange. Understanding when and how to apply different volume types is essential for building reliable distributed systems.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Application&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;emptyDir&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Temporary intra-pod data sharing; data lifecycle tied to pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;PersistentVolumeClaim&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Persistent storage independent of pod lifecycle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReadWriteOnce&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Optimal for single-pod deployments; better performance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ReadWriteMany&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Required for multi-pod scenarios requiring concurrent data access&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Asynchronous Communication&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pods coordinate through file operations without direct dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Component Decoupling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Producers and consumers operate independently; loosely coupled architecture&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These storage patterns form the foundation for building resilient microservices architectures where components scale and restart independently without affecting system cohesion.&lt;/p&gt;




&lt;h2&gt;
  
  
  Additional Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/" rel="noopener noreferrer"&gt;Kubernetes Volumes Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/" rel="noopener noreferrer"&gt;PersistentVolumes and PersistentVolumeClaims&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/storage/storage-classes/" rel="noopener noreferrer"&gt;Storage Classes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Practical examples in the &lt;code&gt;k8s-play/&lt;/code&gt; directory of this project visit the github repository here &lt;a href="https://github.com/bansikah22/daily-json-pipeline" rel="noopener noreferrer"&gt;daily-json-pipeline&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Next Steps
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Deploy and experiment with the provided examples in your cluster&lt;/li&gt;
&lt;li&gt;Evaluate storage classes available in your Kubernetes environment&lt;/li&gt;
&lt;li&gt;Design storage strategies that match your application's data durability requirements&lt;/li&gt;
&lt;li&gt;Implement monitoring and backup procedures for persistent storage&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>devops</category>
      <category>cloudnative</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Building an Amazon EKS Security Baseline</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Fri, 22 May 2026 17:20:07 +0000</pubDate>
      <link>https://dev.to/bansikah/building-an-amazon-eks-security-baseline-jo5</link>
      <guid>https://dev.to/bansikah/building-an-amazon-eks-security-baseline-jo5</guid>
      <description>&lt;p&gt;Amazon EKS makes it easy to start a Kubernetes cluster on AWS, but the real work begins after the control plane is running. Security in EKS is not a single feature or a one-time checklist. It is a layered posture that spans image hygiene, identity, network boundaries, data protection, and runtime monitoring.&lt;/p&gt;

&lt;p&gt;This article walks through a practical security baseline for EKS, the reasoning behind each control, and the lessons that tend to matter most in production. Every section is grounded in working infrastructure: real AWS resources, real Kubernetes manifests, and real verification steps that were exercised against a live cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Secure the Container Images
&lt;/h2&gt;

&lt;p&gt;Every Kubernetes workload begins with an image, which makes image security the natural starting point. If the image is vulnerable or overly privileged, the rest of the stack inherits that weakness before the pod even starts. The fundamentals are straightforward: use minimal base images, run as a non-root user, never set the &lt;code&gt;privileged&lt;/code&gt; flag on a container, scan images before they are pushed, and enforce repository scanning in Amazon ECR. A Dockerfile linter such as Hadolint, run locally or in CI, adds a cheap second line of defense by catching bad patterns before the image is even built.&lt;/p&gt;

&lt;p&gt;The broader lesson is that a secure deployment pipeline starts well before a pod ever reaches the cluster. When the image is small, hardened, and scanned, the attack surface is already much smaller by the time Kubernetes is involved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Control Who Can Access the Cluster
&lt;/h2&gt;

&lt;p&gt;Once the image layer is under control, the next question is identity. EKS uses AWS IAM for authentication and Kubernetes RBAC for authorization, which means access spans two systems at once. EKS Cluster Access Management offers a cleaner way to map IAM principals to Kubernetes permissions and is the preferred path over the older &lt;code&gt;aws-auth&lt;/code&gt; workflow.&lt;/p&gt;

&lt;p&gt;The goal is access that is explicit, scoped, and reversible. Developers and testers should only get the permissions they need, namespace scoping should be preferred over broad cluster access, and cluster-admin style privileges should remain tightly controlled. One detail that is easy to miss: the IAM principal that creates the cluster is automatically a full cluster admin, and that principal should be removed (or replaced) once a proper set of access entries is in place. This is not only about blocking attackers. It also makes internal access easier to understand and audit as teams change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build Network Boundaries
&lt;/h2&gt;

&lt;p&gt;Kubernetes namespaces are helpful, but namespaces alone are not isolation. Pods can still talk to each other unless the network layer is controlled. A solid baseline establishes a default-deny posture and then opens only the traffic that is actually required. That is the difference between a flat pod network and a controlled application boundary.&lt;/p&gt;

&lt;p&gt;In practice, network policy is what stops a compromised pod from moving laterally through the namespace. The model is simple: deny all pod-to-pod traffic by default, allow only the exact paths that are required, and keep frontend, backend, and unknown workloads separated. For workloads that need finer-grained isolation than a node-level security group can provide, EKS also supports security groups for pods, which attach an ENI and its own security group directly to the pod. Network segmentation should be paired with encryption in transit, whether through TLS on a load balancer, end-to-end TLS to the pod, or a service mesh handling mTLS between workloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Remove Application Permissions from the Node Role
&lt;/h2&gt;

&lt;p&gt;One of the most common EKS mistakes is letting workloads inherit permissions from the worker node IAM role. That pattern works until it becomes a privilege escalation problem. Pods should not rely on the node instance profile for application access. Application permissions belong on the workload identity itself.&lt;/p&gt;

&lt;p&gt;Both modern EKS Pod Identity and the older IRSA model can achieve this, and the right choice depends on the workload. The security value is least privilege at the pod level: give the pod only the AWS actions it actually needs, avoid attaching application policies to the node group, and use a service account as the identity boundary. That shift moves AWS permissions away from infrastructure and onto the application that genuinely needs them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Protect Data at Rest and in Use
&lt;/h2&gt;

&lt;p&gt;Data security is more than encrypting a disk. It also includes secrets, key management, and the way applications consume sensitive values. A complete baseline uses KMS-backed encryption for EBS and EFS volumes (and for RDS, when a database sits behind the cluster), envelope encryption for Kubernetes Secrets in &lt;code&gt;etcd&lt;/code&gt;, and secret consumption through mounted volumes instead of environment variables. KMS keys should have automatic rotation enabled so that key material is refreshed on a regular cadence without breaking access to existing ciphertext.&lt;/p&gt;

&lt;p&gt;A distinction worth making early is that Kubernetes Secrets are base64-encoded by default, not encrypted. They have to be protected at the storage layer and, ideally, encrypted again through envelope encryption. Mounted volumes are also a safer way to consume secrets inside pods than environment variables. Environment variables are convenient, but they are far more likely to leak into logs or debugging output. Volume mounts are temporary, isolated, and easier to clean up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitor Runtime Behavior and Audit Everything
&lt;/h2&gt;

&lt;p&gt;This is where the cluster finally becomes observable, and it is the part that should never be skipped. Static controls are essential, but they cannot catch every threat. A pod can pass every build-time check and still become suspicious after it starts running, which is exactly why runtime security matters.&lt;/p&gt;

&lt;p&gt;A strong runtime layer combines EKS control plane logging for audit and authenticator visibility, GuardDuty Runtime Monitoring for threat detection inside running workloads, and CloudWatch alarms for unusual authentication or authorization patterns. CloudTrail Insights complements this on the AWS API side by flagging unusual call patterns originating from inside the VPC, including from pods. The runtime threats that matter most are crypto mining behavior, command-and-control callbacks, metadata service enumeration, and unauthorized access attempts that show up as repeated 401 or 403 responses. The value here is not only detection. It is also accountability. Audit logs make it possible to reconstruct who did what and when, and runtime monitoring surfaces behavior that was never visible during deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Approach Shows in Practice
&lt;/h2&gt;

&lt;p&gt;The main lesson from a full build is that EKS security is cumulative. Image hardening reduces software supply-chain exposure, access management limits who can reach the cluster, network policy limits lateral movement, pod identity limits AWS privilege, encryption limits data exposure, and runtime monitoring limits dwell time and detection gaps.&lt;/p&gt;

&lt;p&gt;That layered approach is what makes the cluster resilient. No single control solves the problem, but together they create a much stronger baseline than any of them on their own.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Practical Security Mindset for EKS
&lt;/h2&gt;

&lt;p&gt;The goal on EKS is not perfection. The goal is to make compromise harder, detection faster, and blast radius smaller. A hardened image, intentional IAM and RBAC boundaries, namespace-aware network policy, workload-bound AWS permissions, encryption at rest, protected secret usage, and early logging together turn a working cluster into a defensible one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;EKS makes it easy to run Kubernetes, but secure Kubernetes still requires deliberate design decisions. The controls are available, and when they are used together they form a strong operational baseline. The path that usually works best starts with image hardening and ends with runtime monitoring, which mirrors how a real security posture should evolve: from build time to deploy time to run time.&lt;/p&gt;

&lt;p&gt;The right question for any EKS environment is not just whether it runs. The more important question is what happens when it is attacked.&lt;/p&gt;




&lt;h2&gt;
  
  
  Let's Connect
&lt;/h2&gt;

&lt;p&gt;If this was useful, or if you are working on something similar and want to compare notes, feel free to reach out.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/bansikah22" rel="noopener noreferrer"&gt;bansikah22&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;LinkedIn: &lt;a href="https://www.linkedin.com/in/tandap-noel-bansikah/" rel="noopener noreferrer"&gt;Tandap Noel Bansikah&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Source code for this baseline: &lt;a href="https://github.com/bansikah22/eks-security" rel="noopener noreferrer"&gt;bansikah22/eks-security&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>security</category>
      <category>aws</category>
    </item>
    <item>
      <title>Kyverno Without the Noise: Practical Kubernetes Policies for Security &amp; Best Practices</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Fri, 23 Jan 2026 17:12:41 +0000</pubDate>
      <link>https://dev.to/bansikah/kyverno-without-the-noise-practical-kubernetes-policies-for-security-best-practices-21i8</link>
      <guid>https://dev.to/bansikah/kyverno-without-the-noise-practical-kubernetes-policies-for-security-best-practices-21i8</guid>
      <description>&lt;p&gt;How to introduce guardrails in Kubernetes using native YAML — without slowing teams down.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Kyverno version note (May 2026):&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This guide uses current Kyverno syntax where validation actions are set at the rule level (&lt;code&gt;validate.failureAction&lt;/code&gt;).&lt;br&gt;&lt;br&gt;
Older policy-level fields like &lt;code&gt;spec.validationFailureAction&lt;/code&gt; are deprecated.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Kubernetes gives teams a lot of power. Too much power, sometimes. One &lt;code&gt;privileged: true&lt;/code&gt; or missing resource limit can quietly turn into a security incident or a production outage.&lt;/p&gt;

&lt;p&gt;That's where Kyverno comes in.&lt;/p&gt;

&lt;p&gt;This article is not a full Kyverno manual. Instead, it's a guided introduction: why Kyverno exists, how it fits into Kubernetes, and a few concrete security-focused examples to get you started. If it clicks, you can go deeper using the official docs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Kyverno — and why should you care?
&lt;/h2&gt;

&lt;p&gt;Kyverno is a Kubernetes-native policy engine. It lets you define rules for your cluster using plain YAML, the same format you already use for Deployments, Services, and Ingresses.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No new language&lt;/li&gt;
&lt;li&gt;No external policy service
&lt;/li&gt;
&lt;li&gt;No complex tooling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If Kubernetes understands YAML, Kyverno understands your policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Kubernetes needs policy enforcement
&lt;/h2&gt;

&lt;p&gt;Kubernetes validates syntax, not intent.&lt;/p&gt;

&lt;p&gt;That means Kubernetes will happily accept:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pods running as root&lt;/li&gt;
&lt;li&gt;Containers without memory limits&lt;/li&gt;
&lt;li&gt;Images pulled from untrusted registries&lt;/li&gt;
&lt;li&gt;Inconsistent labels and annotations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In real clusters — especially multi-team ones — these are not edge cases. They're normal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policies turn expectations into guarantees.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Kyverno fits in the Kubernetes lifecycle
&lt;/h2&gt;

&lt;p&gt;Kyverno runs as an admission controller. Every time someone tries to create, update, or delete a resource, Kyverno gets a chance to inspect it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validate&lt;/strong&gt; it (allow or deny)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutate&lt;/strong&gt; it (fix or enhance it)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate&lt;/strong&gt; other resources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit&lt;/strong&gt; it (report without blocking)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Admission flow (simplified)
&lt;/h3&gt;

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

&lt;p&gt;This happens before anything is stored in etcd.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policies as YAML — and why that matters
&lt;/h2&gt;

&lt;p&gt;Most policy engines introduce a new DSL. Kyverno doesn't.&lt;/p&gt;

&lt;p&gt;A Kyverno policy looks like a Kubernetes resource:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;apiVersion&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;kind&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;metadata&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers can read policies&lt;/li&gt;
&lt;li&gt;Platform teams can review them&lt;/li&gt;
&lt;li&gt;GitOps tools can manage them&lt;/li&gt;
&lt;li&gt;Policies feel like part of the cluster — not an add-on&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The main types of Kyverno policies (quick tour)
&lt;/h2&gt;

&lt;p&gt;Kyverno supports multiple policy types for different use cases:&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Policy Types
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;ClusterPolicy&lt;/strong&gt; - Cluster-wide policies (used in our examples)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Validate, mutate, generate resources&lt;/li&gt;
&lt;li&gt;Verify image signatures and attestations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Policy&lt;/strong&gt; - Namespace-scoped policies&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same capabilities as ClusterPolicy but limited to specific namespaces&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Specialized Policy Types (recent Kyverno releases)
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: these specialized policy resources use the &lt;code&gt;policies.kyverno.io&lt;/code&gt; API group.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;ValidatingPolicy&lt;/strong&gt;: pure validation using CEL-style expressions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ImageValidatingPolicy&lt;/strong&gt;: image-focused validation (signatures/attestations and supply-chain checks).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;MutatingPolicy&lt;/strong&gt;: dedicated mutation policies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GeneratingPolicy&lt;/strong&gt;: dedicated generation policies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeletingPolicy&lt;/strong&gt;: scheduled deletion of matching resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CEL Libraries&lt;/strong&gt;: reusable CEL helpers/functions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Cleanup note
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CleanupPolicy&lt;/strong&gt; is deprecated in current Kyverno docs.&lt;/li&gt;
&lt;li&gt;Prefer &lt;strong&gt;DeletingPolicy&lt;/strong&gt; for new scheduled cleanup implementations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you have older policies, migrate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;spec.validationFailureAction&lt;/code&gt; -&amp;gt; &lt;code&gt;spec.rules[*].validate.failureAction&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spec.validationFailureActionOverrides&lt;/code&gt; -&amp;gt; &lt;code&gt;spec.rules[*].validate.failureActionOverrides&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this guide, we'll focus on &lt;strong&gt;ClusterPolicy&lt;/strong&gt; - the most versatile option that covers validation, mutation, and generation in one resource type.&lt;/p&gt;

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

&lt;p&gt;You'll need a Kubernetes cluster to follow along. Choose one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Minikube:&lt;/strong&gt; &lt;code&gt;minikube start&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;KodeKloud Playground:&lt;/strong&gt; &lt;a href="https://kodekloud.com/public-playgrounds" rel="noopener noreferrer"&gt;kodecloud kubernetes playground&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kind, k3s, or any Kubernetes cluster&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install Kyverno
&lt;/h3&gt;

&lt;p&gt;Multiple installation options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option 1: Direct YAML (recommended for testing)&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;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; https://github.com/kyverno/kyverno/releases/latest/download/install.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 2: Helm&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;helm repo add kyverno https://kyverno.github.io/kyverno/
helm &lt;span class="nb"&gt;install &lt;/span&gt;kyverno kyverno/kyverno &lt;span class="nt"&gt;-n&lt;/span&gt; kyverno &lt;span class="nt"&gt;--create-namespace&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Option 3: Kustomize&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;kubectl apply &lt;span class="nt"&gt;-k&lt;/span&gt; https://github.com/kyverno/kyverno/config/default
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Get the Demo Repository
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/bansikah22/kyverno-demo.git
&lt;span class="nb"&gt;cd &lt;/span&gt;kyverno-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo time: 3 practical Kyverno scenarios
&lt;/h2&gt;

&lt;p&gt;All examples below are ready-to-run and intentionally minimal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 1: Block privileged containers
&lt;/h3&gt;

&lt;p&gt;Running privileged containers is one of the fastest ways to break your security model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy: deny privileged Pods&lt;/strong&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyverno.io/v1&lt;/span&gt;        &lt;span class="c1"&gt;# Kyverno API version&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;ClusterPolicy&lt;/span&gt;              &lt;span class="c1"&gt;# Policy applies cluster-wide&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;disallow-privileged-containers&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;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;no-privileged&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;kinds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;                &lt;span class="c1"&gt;# Apply this rule to Pod resources&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;failureAction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Enforce&lt;/span&gt;   &lt;span class="c1"&gt;# Block resources that fail validation&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Privileged&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;containers&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;not&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;allowed"&lt;/span&gt;
        &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&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;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;privileged&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="c1"&gt;# Require privileged to be false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; the Pod never reaches the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 2: Enforce resource requests and limits
&lt;/h3&gt;

&lt;p&gt;Missing resource limits are a classic reliability problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy: require CPU &amp;amp; memory limits&lt;/strong&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyverno.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;ClusterPolicy&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;require-resource-limits&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;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;check-limits&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;kinds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;                &lt;span class="c1"&gt;# Target Pod resources&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;failureAction&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Enforce&lt;/span&gt;   &lt;span class="c1"&gt;# Block pods without limits&lt;/span&gt;
        &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CPU&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;memory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;limits&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;are&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;required"&lt;/span&gt;
        &lt;span class="na"&gt;pattern&lt;/span&gt;&lt;span class="pi"&gt;:&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;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                    &lt;span class="na"&gt;cpu&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="c1"&gt;# Require any CPU limit value&lt;/span&gt;
                    &lt;span class="na"&gt;memory&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="c1"&gt;# Require any memory limit value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This turns best practices into cluster rules, not documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scenario 3: Let Kyverno fix things automatically
&lt;/h3&gt;

&lt;p&gt;Blocking is good. Helping is better.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Policy: inject a default securityContext&lt;/strong&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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyverno.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;ClusterPolicy&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;add-security-context&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;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;add-non-root&lt;/span&gt;
      &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;kinds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;                &lt;span class="c1"&gt;# Apply to Pod resources&lt;/span&gt;
      &lt;span class="na"&gt;mutate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                    &lt;span class="c1"&gt;# Modify the resource&lt;/span&gt;
        &lt;span class="na"&gt;patchStrategicMerge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;     &lt;span class="c1"&gt;# Merge this config into the Pod&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;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;# Automatically add non-root security&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Developers don't change anything — the cluster becomes safer by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy Scope: ClusterPolicy vs Policy
&lt;/h2&gt;

&lt;p&gt;Kyverno offers two policy scopes:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ClusterPolicy&lt;/strong&gt; (used in our examples):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applies cluster-wide to all namespaces&lt;/li&gt;
&lt;li&gt;Perfect for organization-wide security standards&lt;/li&gt;
&lt;li&gt;Requires cluster-admin permissions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Policy&lt;/strong&gt; (namespace-scoped):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Applies only to resources in a specific namespace&lt;/li&gt;
&lt;li&gt;Ideal for team-specific rules&lt;/li&gt;
&lt;li&gt;Can be managed by namespace admins
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kyverno.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;Policy&lt;/span&gt;                    &lt;span class="c1"&gt;# Namespace-scoped policy&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;team-specific-limits&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;development&lt;/span&gt;        &lt;span class="c1"&gt;# Only applies to 'development' namespace&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# Same rules as ClusterPolicy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose based on your governance model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Audit vs Enforce: adopting Kyverno safely
&lt;/h2&gt;

&lt;p&gt;Kyverno doesn't force you to go all-in.&lt;/p&gt;

&lt;p&gt;You can start in &lt;strong&gt;Audit&lt;/strong&gt; mode:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Policies report violations&lt;/li&gt;
&lt;li&gt;No workloads are blocked&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then move to &lt;strong&gt;Enforce&lt;/strong&gt; when teams are ready.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;validate.failureAction&lt;/code&gt; per rule (&lt;code&gt;Audit&lt;/code&gt; or &lt;code&gt;Enforce&lt;/code&gt;).&lt;br&gt;
The older policy-level &lt;code&gt;spec.validationFailureAction&lt;/code&gt; is deprecated.&lt;/p&gt;

&lt;p&gt;This makes Kyverno realistic for existing clusters.&lt;/p&gt;
&lt;h2&gt;
  
  
  When Kyverno shines — and when it doesn't
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Kyverno is excellent when:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You want Kubernetes-native policies&lt;/li&gt;
&lt;li&gt;You value readability and YAML&lt;/li&gt;
&lt;li&gt;You enforce platform and security standards&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Kyverno is not:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A runtime security tool&lt;/li&gt;
&lt;li&gt;A replacement for vulnerability scanners&lt;/li&gt;
&lt;li&gt;A general-purpose policy engine outside Kubernetes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Knowing this avoids misuse.&lt;/p&gt;
&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If Kyverno makes sense so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explore more policy examples in the &lt;a href="https://kyverno.io/policies/" rel="noopener noreferrer"&gt;official policy library&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Look into image verification and supply-chain security&lt;/li&gt;
&lt;li&gt;Try combining Kyverno with GitOps workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kyverno doesn't try to do everything — it does policy enforcement the Kubernetes way.&lt;/p&gt;

&lt;p&gt;And that's exactly why it works.&lt;/p&gt;
&lt;h2&gt;
  
  
  Hands-on Testing
&lt;/h2&gt;

&lt;p&gt;Now let's test everything together:&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Apply the policies
&lt;/h3&gt;


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

&lt;/div&gt;

&lt;h3&gt;
  
  
  2. Test policy enforcement
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Try the bad pod (should be rejected):&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;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; workloads/bad-pod.yaml
&lt;span class="c"&gt;# Expected: Error - privileged containers not allowed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b4a06hg82ekrf99dyuh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1b4a06hg82ekrf99dyuh.png" alt="bad pod" width="800" height="201"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Deploy the good pod (should succeed):&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;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; workloads/good-pod.yaml
&lt;span class="c"&gt;# Expected: Pod created successfully&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;&lt;strong&gt;Deploy production workload:&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;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; workloads/kubesrv-deployment.yaml
&lt;span class="c"&gt;# Expected: Deployment and service created&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Verify the results
&lt;/h3&gt;



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

&lt;span class="c"&gt;# Test the service (use port 8080 to avoid permission issues)&lt;/span&gt;
kubectl port-forward svc/kubesrv-service 8080:8080

&lt;span class="c"&gt;# In another terminal, test with curl&lt;/span&gt;
curl http://localhost:8080

&lt;span class="c"&gt;## or visit browser with http://localhost:8080 and you will se &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  4. Check policy reports
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# View policy violations&lt;/span&gt;
kubectl get cpol
kubectl describe cpol disallow-privileged-containers
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Kyverno brings policy enforcement to Kubernetes without the complexity. By using familiar YAML syntax and integrating natively with the Kubernetes API, it makes security and governance accessible to every team member.&lt;/p&gt;

&lt;p&gt;The key takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Start small&lt;/strong&gt;: Begin with a few critical policies in Audit mode&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use ClusterPolicy&lt;/strong&gt; for organization-wide standards, &lt;strong&gt;Policy&lt;/strong&gt; for team-specific rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Leverage mutation&lt;/strong&gt; to help teams rather than just block them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Combine with GitOps&lt;/strong&gt; for policy-as-code workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kyverno doesn't try to solve every problem — it solves policy enforcement the Kubernetes way. And that focus is exactly what makes it powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kyverno.io/docs/" rel="noopener noreferrer"&gt;Official Kyverno Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kyverno.io/policies/" rel="noopener noreferrer"&gt;Kyverno Policy Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/kyverno/kyverno" rel="noopener noreferrer"&gt;Kyverno GitHub Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/" rel="noopener noreferrer"&gt;Kubernetes Admission Controllers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bansikah22/kyverno-demo" rel="noopener noreferrer"&gt;Demo Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bansikah22/kubesrv" rel="noopener noreferrer"&gt;KubeServ Test Application&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Ready to implement Kyverno in your cluster? Start with the demo repository and gradually expand your policy coverage. Remember: the best policy is one that helps teams succeed while keeping the cluster secure.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>kyverno</category>
      <category>kubernetes</category>
      <category>security</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>Terraform State Management with Amazon S3</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Wed, 07 Jan 2026 07:02:37 +0000</pubDate>
      <link>https://dev.to/bansikah/terraform-state-management-with-amazon-s3-1478</link>
      <guid>https://dev.to/bansikah/terraform-state-management-with-amazon-s3-1478</guid>
      <description>&lt;p&gt;In our previous article the &lt;a href="https://dev.to/bansikah/amazon-eks-series-part-1-introduction-to-eks-4m3p"&gt;AWS EKS series&lt;/a&gt;, i mentioned that we will later have an article about managing terraform state. Managing Terraform state correctly is one of the most important skills for anyone using Terraform in real-world AWS environments.&lt;/p&gt;

&lt;p&gt;In this article, we'll explore how to store your Terraform state in an Amazon S3 remote backend, why this matters, essential best practices (including state locking), and a hands-on example you can follow.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This guide uses official &lt;a href="https://developer.hashicorp.com/terraform/language/settings/backends/s3" rel="noopener noreferrer"&gt;Terraform documentation&lt;/a&gt; as the source of truth.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why Terraform State Matters
&lt;/h2&gt;

&lt;p&gt;Terraform uses a state file (&lt;code&gt;terraform.tfstate&lt;/code&gt;) to track what infrastructure it manages. This file contains important mappings between your configuration and real resources — so Terraform can make safe and predictable changes in future runs.&lt;/p&gt;

&lt;p&gt;By default, Terraform stores state locally, which works for experimentation but is &lt;strong&gt;unsafe for collaboration, automation, and production use&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Use a Remote Backend like S3?
&lt;/h2&gt;

&lt;p&gt;Using a remote backend like Amazon S3 provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Centralized state accessible by all team members&lt;/li&gt;
&lt;li&gt;Collaboration support&lt;/li&gt;
&lt;li&gt;Encryption and access control via IAM&lt;/li&gt;
&lt;li&gt;Versioning for recovery&lt;/li&gt;
&lt;li&gt;Support for state locking to avoid conflicts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Terraform's official documentation confirms that S3 is a supported backend type that can store state and support locking — and it's one of the most popular and reliable backends used in AWS environments.&lt;/p&gt;




&lt;h2&gt;
  
  
  State Locking — Preventing Conflicts
&lt;/h2&gt;

&lt;p&gt;Terraform needs to prevent two people from writing changes to the same state file at the same time — otherwise state could become corrupted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modern Locking (S3 Native)
&lt;/h3&gt;

&lt;p&gt;Terraform now supports &lt;strong&gt;native state locking in S3&lt;/strong&gt; through a lockfile mechanism. This means Terraform will create a &lt;code&gt;.tflock&lt;/code&gt; file in the bucket alongside your state file, preventing concurrent runs.&lt;/p&gt;

&lt;p&gt;This is done using the optional argument &lt;code&gt;use_lockfile&lt;/code&gt; in your backend config.&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;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-terraform-state-bucket2z"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"envs/prod/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
    &lt;span class="nx"&gt;use_lockfile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;ul&gt;
&lt;li&gt;
&lt;code&gt;use_lockfile = true&lt;/code&gt; enables S3-native locking&lt;/li&gt;
&lt;li&gt;S3 creates a &lt;code&gt;.tflock&lt;/code&gt; file during a Terraform run&lt;/li&gt;
&lt;li&gt;Other processes must wait until the lock is released&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This removes the need for a separate DynamoDB table for state locking — an approach that is officially deprecated and will be removed in future Terraform releases.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; DynamoDB locking is still supported for backward compatibility, but Terraform now prefers S3 native locking for new configurations.&lt;/p&gt;
&lt;/blockquote&gt;




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

&lt;p&gt;Before we begin, make sure you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS CLI installed and configured&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Terraform installed&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Permissions&lt;/strong&gt; to create S3 buckets and apply Terraform changes&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 1 — Create an S3 Bucket for Terraform State
&lt;/h2&gt;

&lt;p&gt;Terraform cannot create the backend bucket for you, so you must create it first.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api create-bucket &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; my-terraform-state-bucket2z &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Replace &lt;code&gt;my-terraform-state-bucket2z&lt;/code&gt; with a globally unique name.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 2 — Enable Bucket Versioning (Best Practice)
&lt;/h2&gt;

&lt;p&gt;Versioning helps you recover from accidental deletions or corruption.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api put-bucket-versioning &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; my-terraform-state-bucket2z &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--versioning-configuration&lt;/span&gt; &lt;span class="nv"&gt;Status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Enabled
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 3 — Enable Encryption on the Bucket
&lt;/h2&gt;

&lt;p&gt;Encrypting state at rest is essential for security:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3api put-bucket-encryption &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; my-terraform-state-bucket2z &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--server-side-encryption-configuration&lt;/span&gt; &lt;span class="s1"&gt;'{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "AES256"
      }
    }]
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Troubleshooting: AccessDenied Error
&lt;/h3&gt;

&lt;p&gt;If you encounter an &lt;code&gt;AccessDenied&lt;/code&gt; error when running the encryption command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;An error occurred (AccessDenied) when calling the PutBucketEncryption operation: Access Denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Common causes and fixes:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Insufficient IAM Permissions&lt;/strong&gt; — Your IAM user/role needs &lt;code&gt;s3:PutEncryptionConfiguration&lt;/code&gt; permission&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bucket doesn't exist&lt;/strong&gt; — Verify the bucket name is correct:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   aws s3api head-bucket &lt;span class="nt"&gt;--bucket&lt;/span&gt; my-terraform-state-bucket2z
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Wrong AWS credentials&lt;/strong&gt; — Verify your identity:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4 — Configure Terraform Backend
&lt;/h2&gt;

&lt;p&gt;In your Terraform directory, define the backend like this:&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;terraform&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-terraform-state-bucket2z"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"demo/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
    &lt;span class="nx"&gt;use_lockfile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&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;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bucket&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The S3 bucket you created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to store the state file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;use_lockfile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enables native S3 state locking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Demo: Using S3 Remote State
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Create a Terraform Project
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;terraform-s3-state-demo
&lt;span class="nb"&gt;cd &lt;/span&gt;terraform-s3-state-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Add Terraform Code
&lt;/h3&gt;

&lt;p&gt;Create a file &lt;code&gt;main.tf&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;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;aws&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/aws"&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; 5.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;backend&lt;/span&gt; &lt;span class="s2"&gt;"s3"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;bucket&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-terraform-state-bucket2z"&lt;/span&gt;
    &lt;span class="nx"&gt;key&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"demo/terraform.tfstate"&lt;/span&gt;
    &lt;span class="nx"&gt;region&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
    &lt;span class="nx"&gt;encrypt&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;use_lockfile&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"example"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tf-s3-backend-demo-example"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Initialize the Backend
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Terraform will initialize and automatically configure the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Apply the Changes
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Your state file is now stored in S3, and state locking is enabled.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Useful Terraform Backend Commands
&lt;/h2&gt;

&lt;p&gt;Here are some helpful Terraform commands for managing remote state:&lt;/p&gt;

&lt;h3&gt;
  
  
  Migrate Local State to Remote
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init &lt;span class="nt"&gt;-migrate-state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Reconfigure Backend
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform init &lt;span class="nt"&gt;-reconfigure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List State Resources
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Inspect a Resource
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform state show aws_s3_bucket.example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Additional Best Practices
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Store State Remotely
&lt;/h3&gt;

&lt;p&gt;Use S3 or Terraform Cloud — never store state locally in team environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't Edit or Delete State Manually
&lt;/h3&gt;

&lt;p&gt;Manual changes corrupt state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Isolate State by Environment
&lt;/h3&gt;

&lt;p&gt;Use different keys for different environments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;envs/dev/terraform.tfstate
envs/staging/terraform.tfstate
envs/prod/terraform.tfstate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Regular Backups
&lt;/h3&gt;

&lt;p&gt;Enable S3 versioning so you can roll back state accidentally overwritten or deleted.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lock State on All Writes
&lt;/h3&gt;

&lt;p&gt;Enable &lt;code&gt;use_lockfile = true&lt;/code&gt;. It ensures only one Terraform process can modify state at a time, protecting against conflicts and corruption.&lt;/p&gt;




&lt;h2&gt;
  
  
  Cleanup: Removing Demo Resources Safely
&lt;/h2&gt;

&lt;p&gt;After completing the demo, it's important to clean up resources to avoid unnecessary AWS costs and to keep your environment tidy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Always destroy Terraform-managed infrastructure before deleting the remote state backend.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 1: Destroy Terraform Resources
&lt;/h3&gt;

&lt;p&gt;From your Terraform project directory, run:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

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

&lt;ul&gt;
&lt;li&gt;Read the state file from S3&lt;/li&gt;
&lt;li&gt;Identify all managed resources&lt;/li&gt;
&lt;li&gt;Safely delete them from AWS&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Confirm when prompted.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;All demo infrastructure is removed&lt;/li&gt;
&lt;li&gt;The Terraform state file still exists in S3 (this is expected)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 2: Verify State File in S3 (Optional)
&lt;/h3&gt;

&lt;p&gt;You can confirm the state file still exists:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;ls &lt;/span&gt;s3://my-terraform-state-bucket2z/demo/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform keeps the state file so you have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A record of managed infrastructure&lt;/li&gt;
&lt;li&gt;Historical versions (if versioning is enabled)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Delete the Terraform State File (Optional)
&lt;/h3&gt;

&lt;p&gt;If this was only a demo and you no longer need the state file, you may delete it after destroying resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://my-terraform-state-bucket2z/demo/terraform.tfstate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; Never delete a state file while infrastructure still exists — this will orphan resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 4: (Optional) Disable or Remove Lock Files
&lt;/h3&gt;

&lt;p&gt;If you used S3 native locking, Terraform automatically removes the &lt;code&gt;.tflock&lt;/code&gt; file after operations complete.&lt;/p&gt;

&lt;p&gt;You normally do not need to manually remove lock files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Delete the S3 Backend Bucket (Optional)
&lt;/h3&gt;

&lt;p&gt;If the S3 bucket was created only for this demo, and you no longer need it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Empty the Bucket:&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;aws s3 &lt;span class="nb"&gt;rm &lt;/span&gt;s3://my-terraform-state-bucket2z &lt;span class="nt"&gt;--recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Delete the Bucket:&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;aws s3api delete-bucket &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--bucket&lt;/span&gt; my-terraform-state-bucket2z &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cleanup Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Always run &lt;code&gt;terraform destroy&lt;/code&gt; first&lt;/li&gt;
&lt;li&gt;Never delete state files before destroying resources&lt;/li&gt;
&lt;li&gt;Keep state buckets for long-term projects&lt;/li&gt;
&lt;li&gt;Enable versioning for recovery&lt;/li&gt;
&lt;li&gt;Restrict access using IAM&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Terraform state is foundational to infrastructure automation.&lt;/p&gt;

&lt;p&gt;Storing it in a remote S3 backend with native state locking enabled:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensures collaboration&lt;/li&gt;
&lt;li&gt;Prevents concurrent modifications&lt;/li&gt;
&lt;li&gt;Allows controlled recovery&lt;/li&gt;
&lt;li&gt;Supports CI/CD workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;S3 remote state with native locking is now a best practice recommended by Terraform — and removes the need for a DynamoDB lock table in most cases.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/settings/backends/s3" rel="noopener noreferrer"&gt;Terraform S3 Backend (Official Docs)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/language/state/locking" rel="noopener noreferrer"&gt;Terraform State Locking (Official Docs)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Terraform state cleanup is just as important as provisioning.&lt;/p&gt;

&lt;p&gt;A safe cleanup process ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No orphaned resources&lt;/li&gt;
&lt;li&gt;No unexpected AWS charges&lt;/li&gt;
&lt;li&gt;Clean environments for future work&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When using remote state: &lt;strong&gt;Terraform is always the source of truth&lt;/strong&gt; — let it manage the lifecycle from start to finish.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>infrastructureascode</category>
      <category>aws</category>
      <category>cloud</category>
    </item>
    <item>
      <title>EKS vs ECS: A Comprehensive Comparison</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Mon, 05 Jan 2026 22:06:18 +0000</pubDate>
      <link>https://dev.to/bansikah/eks-vs-ecs-a-comprehensive-comparison-351i</link>
      <guid>https://dev.to/bansikah/eks-vs-ecs-a-comprehensive-comparison-351i</guid>
      <description>&lt;h2&gt;
  
  
  What Problem Are We Solving?
&lt;/h2&gt;

&lt;p&gt;Imagine you're running a restaurant. You have multiple chefs (containers) cooking different dishes (applications). Now, who decides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Which chef works on which dish?&lt;/li&gt;
&lt;li&gt;What happens if a chef gets sick mid-service?&lt;/li&gt;
&lt;li&gt;How many chefs do you need during the lunch rush vs. a quiet Tuesday afternoon?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the software world, &lt;strong&gt;containers&lt;/strong&gt; are like those chefs—small, independent workers running your applications. And just like a restaurant needs a manager to coordinate everything, containers need a &lt;strong&gt;container orchestrator&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Container Orchestration?
&lt;/h2&gt;

&lt;p&gt;Container orchestration is simply &lt;strong&gt;automated container management&lt;/strong&gt;. Think of it as an intelligent manager that:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;What It Means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Deployment&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deciding where each container should run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scaling&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Adding more containers when traffic spikes, removing them when it's quiet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-healing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Automatically restarting containers that crash&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Load balancing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Spreading incoming traffic evenly across containers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Without orchestration, you'd have to manually start, stop, monitor, and manage potentially hundreds of containers. That's not practical—or fun.&lt;/p&gt;




&lt;h2&gt;
  
  
  Meet the Two AWS Solutions
&lt;/h2&gt;

&lt;p&gt;AWS offers two main container orchestration services:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;ECS&lt;/strong&gt; (Elastic Container Service) — AWS's own solution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EKS&lt;/strong&gt; (Elastic Kubernetes Service) — Managed Kubernetes on AWS&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's break down each one.&lt;/p&gt;




&lt;h2&gt;
  
  
  Amazon ECS: The Simple Choice
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is ECS?
&lt;/h3&gt;

&lt;p&gt;ECS is AWS's &lt;strong&gt;homegrown container orchestration service&lt;/strong&gt;. It's designed to be straightforward and works seamlessly with other AWS services.&lt;/p&gt;

&lt;p&gt;Think of ECS as hiring a property management company that only works with buildings in one city (AWS). They know that city inside and out, making everything smooth—but you can't use them if you ever move to another city.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Does ECS Work?
&lt;/h3&gt;

&lt;p&gt;ECS has two main parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Control Plane&lt;/strong&gt; — The "brain" that decides what runs where (AWS manages this for you—for free!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compute&lt;/strong&gt; — The actual servers that run your containers&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Where Do Your Containers Actually Run?
&lt;/h3&gt;

&lt;p&gt;You have two options:&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 1: EC2 Launch Type
&lt;/h4&gt;

&lt;p&gt;You provide your own virtual servers (EC2 instances), and ECS places containers on them.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;More control over the underlying servers&lt;/li&gt;
&lt;li&gt;Can be cheaper for steady, predictable workloads&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;You manage the servers (updates, security patches, capacity)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Option 2: Fargate Launch Type
&lt;/h4&gt;

&lt;p&gt;AWS handles everything. You just say "run this container" and AWS figures out the rest.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Zero server management&lt;/li&gt;
&lt;li&gt;Pay only for what you use, down to the second&lt;/li&gt;
&lt;li&gt;Perfect for variable workloads&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Can be more expensive for always-on workloads&lt;/li&gt;
&lt;li&gt;Less control over the underlying infrastructure&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Amazon EKS: The Kubernetes Option
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is EKS?
&lt;/h3&gt;

&lt;p&gt;EKS is &lt;strong&gt;Kubernetes managed by AWS&lt;/strong&gt;. Kubernetes (often called "K8s") is the industry-standard container orchestrator, originally created by Google.&lt;/p&gt;

&lt;p&gt;Think of EKS as hiring an internationally certified property management company. They follow global standards, so if you ever move to another city (another cloud provider), your processes stay the same.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Does Kubernetes Exist?
&lt;/h3&gt;

&lt;p&gt;Running Kubernetes yourself is hard. You'd need to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up and secure master nodes (the control plane)&lt;/li&gt;
&lt;li&gt;Install and configure multiple complex components&lt;/li&gt;
&lt;li&gt;Handle upgrades, backups, and high availability&lt;/li&gt;
&lt;li&gt;Keep everything secure and patched&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;EKS takes care of all the hard parts. AWS manages the control plane, and you just focus on running your applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where Do Your Containers Run on EKS?
&lt;/h3&gt;

&lt;p&gt;Just like ECS, you need compute resources. EKS gives you three options:&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 1: Self-Managed Nodes
&lt;/h4&gt;

&lt;p&gt;You create and manage your own EC2 instances, installing all the necessary Kubernetes components yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams needing maximum customization or specific compliance requirements.&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 2: Managed Node Groups (Recommended)
&lt;/h4&gt;

&lt;p&gt;AWS creates and manages EC2 instances for you. Updates and patches happen automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Most production workloads. You get convenience without giving up too much control.&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 3: Fargate
&lt;/h4&gt;

&lt;p&gt;Completely serverless. AWS handles everything—you don't even think about servers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Variable workloads or teams that want zero infrastructure management.&lt;/p&gt;




&lt;h2&gt;
  
  
  ECS vs EKS: The Honest Comparison
&lt;/h2&gt;

&lt;p&gt;Let's compare these services across what actually matters:&lt;/p&gt;

&lt;h3&gt;
  
  
  Learning Curve
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ECS&lt;/th&gt;
&lt;th&gt;EKS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Easier&lt;/strong&gt; — Simple concepts, AWS-native terminology&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Harder&lt;/strong&gt; — Must learn Kubernetes concepts plus AWS specifics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can be productive in days&lt;/td&gt;
&lt;td&gt;May take weeks to become comfortable&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; If you're new to containers, ECS will get you running faster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cost
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ECS&lt;/th&gt;
&lt;th&gt;EKS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Control plane is &lt;strong&gt;free&lt;/strong&gt;
&lt;/td&gt;
&lt;td&gt;Control plane is &lt;strong&gt;paid&lt;/strong&gt; (per cluster per hour)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pay only for compute (EC2 or Fargate)&lt;/td&gt;
&lt;td&gt;Pay for control plane + compute&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; For current EKS pricing, refer to the &lt;a href="https://aws.amazon.com/eks/pricing/" rel="noopener noreferrer"&gt;official AWS EKS Pricing page&lt;/a&gt;. Pricing varies based on Kubernetes version support tier (standard vs. extended).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; ECS is cheaper, especially for smaller deployments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vendor Lock-in
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ECS&lt;/th&gt;
&lt;th&gt;EKS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS-only — your knowledge and configurations don't transfer&lt;/td&gt;
&lt;td&gt;Kubernetes is universal — skills transfer to any cloud&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;If AWS changes ECS, you adapt&lt;/td&gt;
&lt;td&gt;Kubernetes is open-source with a stable API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; EKS gives you more flexibility and portability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Ecosystem and Tooling
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ECS&lt;/th&gt;
&lt;th&gt;EKS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Limited to AWS tools&lt;/td&gt;
&lt;td&gt;Massive ecosystem: Helm, ArgoCD, Istio, Prometheus, and hundreds more&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Smaller community&lt;/td&gt;
&lt;td&gt;Huge global community with endless resources&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; EKS has a richer ecosystem and more community support.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operational Complexity
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;ECS&lt;/th&gt;
&lt;th&gt;EKS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Simpler to operate day-to-day&lt;/td&gt;
&lt;td&gt;More moving parts to understand and manage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Less configuration needed&lt;/td&gt;
&lt;td&gt;More powerful, but more configuration required&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Bottom line:&lt;/strong&gt; ECS is easier to operate; EKS offers more power at the cost of complexity.&lt;/p&gt;




&lt;h2&gt;
  
  
  Making Your Decision: A Simple Framework
&lt;/h2&gt;

&lt;p&gt;Ask yourself these questions:&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose ECS if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;You're new to containers and want to start quickly
&lt;/li&gt;
&lt;li&gt;Your team is small and doesn't have Kubernetes experience
&lt;/li&gt;
&lt;li&gt;You're fully committed to AWS with no plans to use other clouds
&lt;/li&gt;
&lt;li&gt;You want the simplest possible setup
&lt;/li&gt;
&lt;li&gt;Cost optimization is a top priority
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Choose EKS if:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Your team already knows Kubernetes
&lt;/li&gt;
&lt;li&gt;You might use multiple clouds in the future (AWS, Azure, GCP)
&lt;/li&gt;
&lt;li&gt;You need advanced features like service mesh, custom controllers, or complex networking
&lt;/li&gt;
&lt;li&gt;You want to use popular K8s tools (Helm, ArgoCD, etc.)
&lt;/li&gt;
&lt;li&gt;You're running large-scale, complex microservices
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Real-World Scenarios
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scenario 1: Startup Building Their First App
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Situation:&lt;/strong&gt; A 5-person startup launching their first web application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; &lt;strong&gt;ECS with Fargate&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; They need to move fast, have limited DevOps experience, and shouldn't spend time managing infrastructure. ECS is simpler, and Fargate means zero server management.&lt;/p&gt;




&lt;h3&gt;
  
  
  Scenario 2: Enterprise Migrating from On-Premises
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Situation:&lt;/strong&gt; A large company moving their existing Kubernetes workloads from on-premises data centers to AWS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; &lt;strong&gt;EKS with Managed Node Groups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; They already have Kubernetes expertise and existing configurations. EKS lets them reuse their knowledge and tooling with minimal changes.&lt;/p&gt;




&lt;h3&gt;
  
  
  Scenario 3: Company with Multi-Cloud Strategy
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Situation:&lt;/strong&gt; A company that uses AWS primarily but also deploys to Azure for specific clients.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; &lt;strong&gt;EKS&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Why?&lt;/em&gt; Kubernetes skills and configurations work across clouds. They can use similar practices on AWS (EKS) and Azure (AKS).&lt;/p&gt;




&lt;h2&gt;
  
  
  Quick Reference Cheat Sheet
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;ECS&lt;/th&gt;
&lt;th&gt;EKS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;How hard is it to learn?&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Moderate to Hard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;What does the control plane cost?&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Paid (per cluster per hour)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Can I use it on other clouds?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (Kubernetes is universal)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;How big is the community?&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Massive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for beginners?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Not ideal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for Kubernetes users?&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Serverless option?&lt;/td&gt;
&lt;td&gt;Fargate&lt;/td&gt;
&lt;td&gt;Fargate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




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

&lt;p&gt;There's no universally "better" choice between ECS and EKS—only what's better &lt;strong&gt;for your situation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ECS&lt;/strong&gt; is like an automatic car: easy to learn, gets you where you need to go, and requires less attention. Perfect for most drivers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EKS&lt;/strong&gt; is like a manual car: more control, more powerful in the right hands, but requires more skill to operate well. Preferred by enthusiasts and professionals.&lt;/p&gt;

&lt;p&gt;Start with what matches your team's skills and your project's needs. You can always evolve your choice as your requirements grow.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/eks/pricing/" rel="noopener noreferrer"&gt;Amazon EKS Pricing&lt;/a&gt; - Official AWS pricing page for EKS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/ecs/pricing/" rel="noopener noreferrer"&gt;Amazon ECS Pricing&lt;/a&gt; - Official AWS pricing page for ECS&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://aws.amazon.com/fargate/pricing/" rel="noopener noreferrer"&gt;AWS Fargate Pricing&lt;/a&gt; - Official AWS pricing page for Fargate&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/eks/" rel="noopener noreferrer"&gt;Amazon EKS Documentation&lt;/a&gt; - Official EKS documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.aws.amazon.com/ecs/" rel="noopener noreferrer"&gt;Amazon ECS Documentation&lt;/a&gt; - Official ECS documentation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://kubernetes.io/docs/" rel="noopener noreferrer"&gt;Kubernetes Documentation&lt;/a&gt; - Official Kubernetes documentation&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>cloudnative</category>
      <category>devops</category>
    </item>
    <item>
      <title>Deploying Applications on Amazon ECS: A Practical Guide</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Fri, 19 Dec 2025 08:47:58 +0000</pubDate>
      <link>https://dev.to/bansikah/deploying-applications-on-amazon-ecs-a-practical-guide-5gm4</link>
      <guid>https://dev.to/bansikah/deploying-applications-on-amazon-ecs-a-practical-guide-5gm4</guid>
      <description>&lt;h1&gt;
  
  
  Deploying Applications on Amazon ECS: A Practical Guide
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In this article, I will walk you through deploying a containerized application on &lt;strong&gt;Amazon ECS (Elastic Container Service)&lt;/strong&gt; using AWS Fargate. We will use a real full-stack application called &lt;strong&gt;CloudNotes&lt;/strong&gt; that has already been developed and dockerized.&lt;/p&gt;

&lt;p&gt;Our focus will be entirely on ECS concepts, the deployment process, and understanding the Terraform infrastructure code. By the end of this article, you will have a clear understanding of how ECS works and how to deploy your own containerized applications on AWS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href="https://github.com/bansikah22/cloudnotes" rel="noopener noreferrer"&gt;github.com/bansikah22/cloudnotes&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Amazon ECS?
&lt;/h2&gt;

&lt;p&gt;Amazon ECS (Elastic Container Service) is a fully managed container orchestration service provided by AWS. It manages how your Docker containers are defined, deployed, scaled, and maintained.&lt;/p&gt;

&lt;p&gt;ECS handles the complexity of running containers at scale while integrating deeply with other AWS services like IAM, CloudWatch, VPC, and Application Load Balancers.&lt;/p&gt;




&lt;h2&gt;
  
  
  ECS Core Concepts
&lt;/h2&gt;

&lt;p&gt;Understanding ECS requires grasping its main objects and how they relate to each other:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Cluster
&lt;/h3&gt;

&lt;p&gt;An ECS Cluster is a logical grouping of tasks or services. It serves as the foundation where your container workloads run. You can think of it as a namespace that organizes your services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task Definition
&lt;/h3&gt;

&lt;p&gt;A Task Definition is a blueprint that describes how containers should be deployed. It is unique to ECS and defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How much CPU and memory a container will use&lt;/li&gt;
&lt;li&gt;The container image to pull&lt;/li&gt;
&lt;li&gt;Port mappings&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;li&gt;Volumes and other runtime settings&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Essentially, a Task Definition contains the same configuration you would normally place in a &lt;code&gt;docker-compose.yml&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important Note&lt;/strong&gt;: Port mapping in ECS (especially with &lt;code&gt;awsvpc&lt;/code&gt; network mode on Fargate) must map the same port to the same port (e.g., &lt;code&gt;3000:3000&lt;/code&gt;). Mappings like &lt;code&gt;3000:3001&lt;/code&gt; will not work because ECS does not allow arbitrary host-to-container remapping in this mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Task
&lt;/h3&gt;

&lt;p&gt;A Task is an instance of a Task Definition. It represents a running copy of your application. The Task contains one or more running containers defined by the Task Definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service
&lt;/h3&gt;

&lt;p&gt;An ECS Service ensures that a specified number of Tasks are running at all times. It provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automatic restart of tasks if they crash or exit&lt;/li&gt;
&lt;li&gt;Rescheduling of tasks on healthy instances if an EC2 instance fails (in EC2 mode)&lt;/li&gt;
&lt;li&gt;Integration with load balancers to distribute traffic across tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ECS Load Balancers
&lt;/h3&gt;

&lt;p&gt;An Application Load Balancer (ALB) can be assigned to an ECS Service. It routes external traffic to tasks through a Target Group. The ECS service automatically registers and deregisters tasks to the Target Group as they start and stop.&lt;/p&gt;

&lt;h3&gt;
  
  
  Launch Types: Fargate vs EC2
&lt;/h3&gt;

&lt;p&gt;ECS offers two launch types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Launch Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Fargate&lt;/td&gt;
&lt;td&gt;Serverless - AWS manages the underlying infrastructure&lt;/td&gt;
&lt;td&gt;Most workloads, simpler operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EC2&lt;/td&gt;
&lt;td&gt;You manage the EC2 instances that host your containers&lt;/td&gt;
&lt;td&gt;GPU workloads, specific instance requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;For this guide, we use Fargate because it eliminates server management, you pay only for resources used, and it handles infrastructure scaling automatically.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CloudNotes Application
&lt;/h2&gt;

&lt;p&gt;The application we are deploying is a full-stack ToDo Notes application. It has already been developed and dockerized, so our focus remains on the ECS deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tech Stack&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend: React, Vite, TypeScript, served via Nginx&lt;/li&gt;
&lt;li&gt;Backend: Node.js, Express, TypeScript&lt;/li&gt;
&lt;li&gt;Infrastructure: Terraform, AWS ECS Fargate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Docker images are available on Docker Hub:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bansikah/cloudnotes-frontend:latest&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bansikah/cloudnotes-backend:latest&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;To follow along with this guide, clone the repository and explore the infrastructure code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/bansikah22/cloudnotes.git
&lt;span class="nb"&gt;cd &lt;/span&gt;cloudnotes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;Before deploying, ensure you have:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS CLI installed and configured with appropriate credentials (&lt;code&gt;aws configure&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Terraform v1.0+ installed&lt;/li&gt;
&lt;li&gt;Docker installed (for local testing if needed)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Understanding the Terraform Infrastructure
&lt;/h2&gt;

&lt;p&gt;The infrastructure code is located in the &lt;code&gt;infrastructure/&lt;/code&gt; directory. Let me walk you through the key components.&lt;/p&gt;

&lt;h3&gt;
  
  
  VPC Configuration
&lt;/h3&gt;

&lt;p&gt;We use the terraform-aws-modules VPC module to create a network with public and private subnets across two availability zones:&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;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&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;"terraform-aws-modules/vpc/aws"&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; 5.0"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudnotes-vpc"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&lt;/span&gt;

  &lt;span class="nx"&gt;azs&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;public_subnets&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.1.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"10.0.101.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"10.0.102.0/24"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;enable_nat_gateway&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;single_nat_gateway&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ALB sits in public subnets while ECS tasks run in private subnets, accessing the internet through the NAT Gateway.&lt;/p&gt;

&lt;h3&gt;
  
  
  ECS Cluster
&lt;/h3&gt;

&lt;p&gt;The cluster is simply a logical grouping:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_cluster"&lt;/span&gt; &lt;span class="s2"&gt;"cloudnotes"&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="s2"&gt;"cloudnotes-cluster"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Task Definitions
&lt;/h3&gt;

&lt;p&gt;Here is the backend task definition showing how we define container configurations:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_task_definition"&lt;/span&gt; &lt;span class="s2"&gt;"backend"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;family&lt;/span&gt;                   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"cloudnotes-backend"&lt;/span&gt;
  &lt;span class="nx"&gt;requires_compatibilities&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;network_mode&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awsvpc"&lt;/span&gt;
  &lt;span class="nx"&gt;cpu&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;backend_cpu&lt;/span&gt;
  &lt;span class="nx"&gt;memory&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;backend_memory&lt;/span&gt;
  &lt;span class="nx"&gt;execution_role_arn&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_execution_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;task_role_arn&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ecs_task_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;container_definitions&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&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;backend_container&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;The container definition includes the image, port mappings, environment variables, and logging configuration:&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;backend_container&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;name&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"backend"&lt;/span&gt;
    &lt;span class="nx"&gt;image&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;backend_image&lt;/span&gt;
    &lt;span class="nx"&gt;essential&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="nx"&gt;portMappings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;containerPort&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
        &lt;span class="nx"&gt;hostPort&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
        &lt;span class="nx"&gt;protocol&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;name&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"NODE_ENV"&lt;/span&gt;
        &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"production"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;logConfiguration&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;logDriver&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"awslogs"&lt;/span&gt;
      &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-group"&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_cloudwatch_log_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-region"&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;aws_region&lt;/span&gt;
        &lt;span class="s2"&gt;"awslogs-stream-prefix"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"backend"&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="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ECS Services
&lt;/h3&gt;

&lt;p&gt;The service ensures our tasks are always running and connects them to the load balancer:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_ecs_service"&lt;/span&gt; &lt;span class="s2"&gt;"backend"&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="s2"&gt;"cloudnotes-backend"&lt;/span&gt;
  &lt;span class="nx"&gt;cluster&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudnotes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
  &lt;span class="nx"&gt;task_definition&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_ecs_task_definition&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;desired_count&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;desired_count&lt;/span&gt;
  &lt;span class="nx"&gt;launch_type&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"FARGATE"&lt;/span&gt;

  &lt;span class="nx"&gt;network_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;subnets&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;assign_public_ip&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;load_balancer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="nx"&gt;container_name&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"backend"&lt;/span&gt;
    &lt;span class="nx"&gt;container_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;health_check_grace_period_seconds&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;

  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_lb_listener_rule&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_path&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;h3&gt;
  
  
  Application Load Balancer with Path-Based Routing
&lt;/h3&gt;

&lt;p&gt;The ALB distributes traffic between frontend and backend services using path-based routing:&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;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb"&lt;/span&gt; &lt;span class="s2"&gt;"cloudnotes"&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="s2"&gt;"cloudnotes-alb"&lt;/span&gt;
  &lt;span class="nx"&gt;internal&lt;/span&gt;           &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"application"&lt;/span&gt;
  &lt;span class="nx"&gt;security_groups&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;subnets&lt;/span&gt;            &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public_subnets&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_listener"&lt;/span&gt; &lt;span class="s2"&gt;"http"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;load_balancer_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudnotes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;port&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"80"&lt;/span&gt;
  &lt;span class="nx"&gt;protocol&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"HTTP"&lt;/span&gt;

  &lt;span class="nx"&gt;default_action&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="s2"&gt;"forward"&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;frontend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_lb_listener_rule"&lt;/span&gt; &lt;span class="s2"&gt;"api_path"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;listener_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_listener&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;http&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="nx"&gt;priority&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

  &lt;span class="nx"&gt;action&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="s2"&gt;"forward"&lt;/span&gt;
    &lt;span class="nx"&gt;target_group_arn&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_lb_target_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;condition&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;path_pattern&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"/api/*"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This routes all &lt;code&gt;/api/*&lt;/code&gt; requests to the backend service and everything else to the frontend.&lt;/p&gt;

&lt;h3&gt;
  
  
  Security Groups
&lt;/h3&gt;

&lt;p&gt;The security groups control network access:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ALB security group allows inbound HTTP/HTTPS from the internet&lt;/li&gt;
&lt;li&gt;Frontend security group allows traffic only from the ALB on port 80&lt;/li&gt;
&lt;li&gt;Backend security group allows traffic only from the ALB on port 5000
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_security_group"&lt;/span&gt; &lt;span class="s2"&gt;"backend"&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="s2"&gt;"cloudnotes-backend-sg"&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow only ALB to access backend"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;

  &lt;span class="nx"&gt;ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"tcp"&lt;/span&gt;
    &lt;span class="nx"&gt;security_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;aws_security_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;alb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;egress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;from_port&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;to_port&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nx"&gt;protocol&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"-1"&lt;/span&gt;
    &lt;span class="nx"&gt;cidr_blocks&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"0.0.0.0/0"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  IAM Roles
&lt;/h3&gt;

&lt;p&gt;Two IAM roles are created:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Task Execution Role&lt;/strong&gt;: Allows ECS to pull images and write logs to CloudWatch&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task Role&lt;/strong&gt;: Grants permissions to the running application itself&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Deploying the Infrastructure
&lt;/h2&gt;

&lt;p&gt;Navigate to the infrastructure directory and run:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c"&gt;# Initialize Terraform&lt;/span&gt;
terraform init

&lt;span class="c"&gt;# Validate the configuration&lt;/span&gt;
terraform validate

&lt;span class="c"&gt;# Review the execution plan&lt;/span&gt;
terraform plan

&lt;span class="c"&gt;# Deploy the infrastructure&lt;/span&gt;
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; when prompted to confirm the deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Observing the Deployed Application
&lt;/h2&gt;

&lt;p&gt;Once the deployment completes, Terraform outputs the ALB DNS name. You can access your application using this URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  ECS Cluster
&lt;/h3&gt;

&lt;p&gt;The cluster dashboard shows your services and their status:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Running Services
&lt;/h3&gt;

&lt;p&gt;Both frontend and backend services are running with their desired task counts:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Task Definition
&lt;/h3&gt;

&lt;p&gt;The task definition view shows container configurations, resource allocations, and logging settings:&lt;/p&gt;

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

&lt;h3&gt;
  
  
  The Running Application
&lt;/h3&gt;

&lt;p&gt;Access the application via the ALB DNS name to see CloudNotes in action:&lt;/p&gt;

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




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

&lt;p&gt;The architecture follows a standard pattern for containerized applications on AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Users access the application through the Application Load Balancer&lt;/li&gt;
&lt;li&gt;The ALB sits in public subnets and routes traffic based on URL paths&lt;/li&gt;
&lt;li&gt;Frontend requests go to the frontend ECS service&lt;/li&gt;
&lt;li&gt;API requests (&lt;code&gt;/api/*&lt;/code&gt;) go to the backend ECS service&lt;/li&gt;
&lt;li&gt;Both services run as Fargate tasks in private subnets&lt;/li&gt;
&lt;li&gt;Tasks pull images from Docker Hub and send logs to CloudWatch&lt;/li&gt;
&lt;li&gt;A NAT Gateway enables outbound internet access for the private subnets&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Clean Up
&lt;/h2&gt;

&lt;p&gt;To avoid ongoing AWS charges, destroy the infrastructure when you are done:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Amazon ECS is and its core components (Cluster, Service, Task Definition, Task)&lt;/li&gt;
&lt;li&gt;How traffic flows from users through the ALB to ECS services&lt;/li&gt;
&lt;li&gt;The Terraform code that provisions the entire infrastructure&lt;/li&gt;
&lt;li&gt;How to deploy and observe a running application on ECS Fargate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ECS provides a straightforward way to run containers on AWS without managing the underlying infrastructure. With Fargate, you can focus entirely on your application while AWS handles server provisioning, scaling, and maintenance.&lt;/p&gt;




&lt;h2&gt;
  
  
  What is Next
&lt;/h2&gt;

&lt;p&gt;In upcoming articles, we will explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ECS vs EKS: A detailed comparison to help you choose the right service&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Repository: &lt;a href="https://github.com/bansikah22/cloudnotes" rel="noopener noreferrer"&gt;github.com/bansikah22/cloudnotes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS ECS Documentation: &lt;a href="https://docs.aws.amazon.com/ecs/" rel="noopener noreferrer"&gt;docs.aws.amazon.com/ecs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Terraform AWS Provider: &lt;a href="https://registry.terraform.io/providers/hashicorp/aws/latest/docs" rel="noopener noreferrer"&gt;registry.terraform.io/providers/hashicorp/aws&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;If you have questions or feedback, feel free to drop a comment below. If you found this helpful, consider following for more AWS and DevOps content.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ecs</category>
      <category>docker</category>
      <category>devops</category>
    </item>
    <item>
      <title>Amazon EKS Series - Part 5: GitOps with Argo CD on EKS</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Thu, 18 Dec 2025 14:54:00 +0000</pubDate>
      <link>https://dev.to/bansikah/amazon-eks-series-part-5-gitops-with-argo-cd-on-eks-4992</link>
      <guid>https://dev.to/bansikah/amazon-eks-series-part-5-gitops-with-argo-cd-on-eks-4992</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to the final part of the &lt;strong&gt;Amazon EKS at Scale&lt;/strong&gt; series!&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/bansikah/amazon-eks-series-part-4-deploying-applications-on-amazon-eks-9f6"&gt;Part 4&lt;/a&gt;, we deployed applications manually using &lt;code&gt;kubectl&lt;/code&gt; and explored Helm for package management. While these approaches work, they have limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manual processes&lt;/strong&gt; are error-prone&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No audit trail&lt;/strong&gt; of who deployed what and when&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drift&lt;/strong&gt; can occur between what's in Git and what's running&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollbacks&lt;/strong&gt; require remembering previous configurations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this article, we'll solve these problems by implementing &lt;strong&gt;GitOps&lt;/strong&gt; with &lt;strong&gt;Argo CD&lt;/strong&gt;. You'll learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Understand GitOps principles and benefits&lt;/li&gt;
&lt;li&gt;Install and configure Argo CD on EKS&lt;/li&gt;
&lt;li&gt;Deploy a real application (CloudNotes) using GitOps&lt;/li&gt;
&lt;li&gt;Enable automated sync and self-healing&lt;/li&gt;
&lt;li&gt;Observe deployments through the Argo CD UI&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;EKS cluster from Part 3 running&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubectl&lt;/code&gt; configured and working&lt;/li&gt;
&lt;li&gt;Basic understanding of Kubernetes Deployments and Services
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verify cluster connection&lt;/span&gt;
kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvmsm6cd7bknnqh1cbqo7.png" alt="Get Nodes" width="800" height="190"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  What Is GitOps?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;GitOps&lt;/strong&gt; is an operational framework that uses Git as the single source of truth for declarative infrastructure and applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Core Idea
&lt;/h3&gt;

&lt;p&gt;Instead of running commands to deploy applications:&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="c"&gt;# Traditional approach&lt;/span&gt;
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With GitOps, you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Commit&lt;/strong&gt; your desired state to Git&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An agent&lt;/strong&gt; (like Argo CD) watches the repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatically&lt;/strong&gt; applies changes to the cluster&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  GitOps Principles
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Principle&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Declarative&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The entire system is described declaratively in Git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Versioned&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;All changes are tracked with Git history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Automated&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Approved changes are applied automatically&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Self-healing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The system corrects drift from the desired state&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Desired State vs Actual State
&lt;/h3&gt;

&lt;p&gt;GitOps continuously compares:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Desired State&lt;/strong&gt;: What's defined in your Git repository&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actual State&lt;/strong&gt;: What's currently running in the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If they differ, GitOps reconciles by applying the desired state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────┐         ┌─────────────────┐
│   Git Repo      │         │   Kubernetes    │
│  (Desired State)│◄───────►│  (Actual State) │
└─────────────────┘  Sync   └─────────────────┘
         │                           │
         └───────── Argo CD ─────────┘
              (Reconciliation)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Why GitOps Matters for EKS
&lt;/h2&gt;

&lt;p&gt;GitOps is particularly powerful for Amazon EKS environments:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Auditability
&lt;/h3&gt;

&lt;p&gt;Every deployment is a Git commit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who made the change?&lt;/li&gt;
&lt;li&gt;When was it made?&lt;/li&gt;
&lt;li&gt;What exactly changed?&lt;/li&gt;
&lt;li&gt;Why? (commit message)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is invaluable for compliance and debugging.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Easy Rollbacks
&lt;/h3&gt;

&lt;p&gt;Made a bad deployment? Simply revert the Git commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git revert HEAD
git push
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Argo CD automatically rolls back the cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Team Collaboration
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use pull requests for deployment reviews&lt;/li&gt;
&lt;li&gt;Enforce approval workflows&lt;/li&gt;
&lt;li&gt;No need to share cluster credentials widely&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Reduced Human Error
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;No more typos in &lt;code&gt;kubectl&lt;/code&gt; commands&lt;/li&gt;
&lt;li&gt;No forgotten flags or wrong contexts&lt;/li&gt;
&lt;li&gt;Consistent deployments every time&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Disaster Recovery
&lt;/h3&gt;

&lt;p&gt;Your entire cluster state is in Git. If you lose the cluster:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new EKS cluster&lt;/li&gt;
&lt;li&gt;Install Argo CD&lt;/li&gt;
&lt;li&gt;Point to your Git repos&lt;/li&gt;
&lt;li&gt;Everything redeploys automatically&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Introducing Argo CD
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Argo CD&lt;/strong&gt; is a declarative, GitOps continuous delivery tool for Kubernetes.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Argo CD Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Runs inside your cluster&lt;/strong&gt; as a set of Kubernetes controllers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watches Git repositories&lt;/strong&gt; for changes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compares&lt;/strong&gt; desired state (Git) with actual state (cluster)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Syncs&lt;/strong&gt; the cluster to match Git (manually or automatically)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reports&lt;/strong&gt; status through UI, CLI, and API&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Argo CD vs kubectl/Helm
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;kubectl/Helm&lt;/th&gt;
&lt;th&gt;Argo CD&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Deployment trigger&lt;/td&gt;
&lt;td&gt;Manual command&lt;/td&gt;
&lt;td&gt;Git commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State tracking&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Continuous&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Drift detection&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rollback&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Git revert&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit trail&lt;/td&gt;
&lt;td&gt;External logging&lt;/td&gt;
&lt;td&gt;Git history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;UI&lt;/td&gt;
&lt;td&gt;None (CLI only)&lt;/td&gt;
&lt;td&gt;Web dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Argo CD Architecture (Simplified)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────┐
│                    Argo CD                           │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐  │
│  │ API Server  │  │ Repo Server │  │ Application │  │
│  │  (UI/CLI)   │  │ (Git sync)  │  │ Controller  │  │
│  └─────────────┘  └─────────────┘  └─────────────┘  │
└──────────────────────────────────────────────────────┘
         │                  │                │
         ▼                  ▼                ▼
    ┌─────────┐       ┌──────────┐    ┌───────────────┐
    │ Web UI  │       │   Git    │    │  Kubernetes   │
    │   CLI   │       │  Repos   │    │    Cluster    │
    └─────────┘       └──────────┘    └───────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Server&lt;/strong&gt;: Provides the web UI and CLI interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repo Server&lt;/strong&gt;: Clones and caches Git repositories&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Controller&lt;/strong&gt;: Monitors applications and syncs state&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Installing Argo CD on EKS
&lt;/h2&gt;

&lt;p&gt;Let's install Argo CD on our EKS cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create Namespace
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Install Argo CD
&lt;/h3&gt;

&lt;p&gt;We'll use the official installation manifests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Argo CD API Server&lt;/li&gt;
&lt;li&gt;Argo CD Repo Server&lt;/li&gt;
&lt;li&gt;Argo CD Application Controller&lt;/li&gt;
&lt;li&gt;Redis (for caching)&lt;/li&gt;
&lt;li&gt;Dex (for authentication)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 3: Wait for Pods to be Ready
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglepqian4tnggdx8mb9l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fglepqian4tnggdx8mb9l.png" alt="Get argocd pods" width="800" height="190"&gt;&lt;/a&gt;&lt;br&gt;
Wait until all pods show &lt;code&gt;Running&lt;/code&gt; status (1-2 minutes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                               READY   STATUS    RESTARTS   AGE
argocd-application-controller-0                    1/1     Running   0          60s
argocd-applicationset-controller-5f7b9d6b4-xxxxx   1/1     Running   0          60s
argocd-dex-server-6dcf4f8d4b-xxxxx                 1/1     Running   0          60s
argocd-notifications-controller-5c8d7b4c6d-xxxxx   1/1     Running   0          60s
argocd-redis-6976fc7dfc-xxxxx                      1/1     Running   0          60s
argocd-repo-server-7b8d7c4f5-xxxxx                 1/1     Running   0          60s
argocd-server-5f8d7c4f5b-xxxxx                     1/1     Running   0          60s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press &lt;code&gt;Ctrl+C&lt;/code&gt; to stop watching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Expose the Argo CD Server
&lt;/h3&gt;

&lt;p&gt;For this tutorial, we'll use port-forwarding. In production, you'd use an Ingress or LoadBalancer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/argocd-server &lt;span class="nt"&gt;-n&lt;/span&gt; argocd 8080:443
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Keep this terminal open. The Argo CD UI is now available at: &lt;code&gt;https://localhost:8080&lt;/code&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Get the Initial Admin Password
&lt;/h3&gt;

&lt;p&gt;Argo CD generates a random password for the &lt;code&gt;admin&lt;/code&gt; user:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;argocd admin initial-password &lt;span class="nt"&gt;-n&lt;/span&gt; argocd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or using kubectl:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; argocd get secret argocd-initial-admin-secret &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{.data.password}"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Save this password - you'll need it to log in.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1ii887vsapdlns1wmwx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1ii887vsapdlns1wmwx.png" alt="Login" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 6: Access the UI
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Open &lt;code&gt;https://localhost:8080&lt;/code&gt; in your browser&lt;/li&gt;
&lt;li&gt;Accept the self-signed certificate warning&lt;/li&gt;
&lt;li&gt;Log in with:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Username&lt;/strong&gt;: &lt;code&gt;admin&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password&lt;/strong&gt;: (from Step 5)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You should see the Argo CD dashboard - currently empty since we haven't deployed any applications yet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2mxublhsa4n757mzjkr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq2mxublhsa4n757mzjkr.png" alt="Argo Dashboard" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 7: Install Argo CD CLI (Optional but Recommended)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-sSL&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; argocd-linux-amd64 https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-amd64
&lt;span class="nb"&gt;sudo install&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; 555 argocd-linux-amd64 /usr/local/bin/argocd
&lt;span class="nb"&gt;rm &lt;/span&gt;argocd-linux-amd64

&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;argocd

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
argocd version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Login via CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;argocd login localhost:8080 &lt;span class="nt"&gt;--username&lt;/span&gt; admin &lt;span class="nt"&gt;--password&lt;/span&gt; &amp;lt;your-password&amp;gt; &lt;span class="nt"&gt;--insecure&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The CloudNotes Application
&lt;/h2&gt;

&lt;p&gt;For this tutorial, we'll deploy &lt;strong&gt;CloudNotes&lt;/strong&gt; - a full-stack ToDo Notes application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Application Overview
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repository&lt;/strong&gt;: &lt;a href="https://github.com/bansikah22/cloudnotes" rel="noopener noreferrer"&gt;github.com/bansikah22/cloudnotes&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: React + Vite + TypeScript (served by Nginx)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend&lt;/strong&gt;: Node.js + Express + TypeScript&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Images&lt;/strong&gt;: Available on Docker Hub&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why CloudNotes?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real application&lt;/strong&gt; - Not just a demo&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multiple services&lt;/strong&gt; - Frontend and Backend&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already containerized&lt;/strong&gt; - Docker images ready&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demonstrates GitOps&lt;/strong&gt; - Multiple manifests to sync&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Preparing Kubernetes Manifests for GitOps
&lt;/h2&gt;

&lt;p&gt;For GitOps, we need Kubernetes manifests stored in a Git repository. We'll create them in our EKS series repository.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;argocd/
├── README.md
├── applications/
│   └── cloudnotes.yaml          # Argo CD Application resource
└── cloudnotes/
    ├── namespace.yaml           # Kubernetes namespace
    ├── nginx-configmap.yaml     # Nginx config for API proxy
    ├── backend-deployment.yaml  # Backend deployment
    ├── backend-service.yaml     # Backend service (ClusterIP)
    ├── frontend-deployment.yaml # Frontend deployment
    └── frontend-service.yaml    # Frontend service (LoadBalancer)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This separation keeps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Argo CD configuration&lt;/strong&gt; in &lt;code&gt;applications/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application manifests&lt;/strong&gt; in &lt;code&gt;cloudnotes/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Writing the Kubernetes Manifests
&lt;/h2&gt;

&lt;p&gt;Let's create the manifests for CloudNotes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Namespace
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;argocd/cloudnotes/namespace.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;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;Namespace&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;cloudnotes&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
    &lt;span class="na"&gt;managed-by&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backend Deployment
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;argocd/cloudnotes/backend-deployment.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes-backend&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
        &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&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;backend&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;bansikah/cloudnotes-backend:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
        &lt;span class="na"&gt;env&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;NODE_ENV&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;production"&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;PORT&lt;/span&gt;
          &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000"&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;500m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
        &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/health&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
          &lt;span class="na"&gt;initialDelaySeconds&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;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/health&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
          &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
          &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Backend Service
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;argocd/cloudnotes/backend-service.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes-backend&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The backend uses &lt;code&gt;ClusterIP&lt;/code&gt; (internal only). The frontend's Nginx will proxy API requests to this service.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Nginx ConfigMap (API Proxy)
&lt;/h3&gt;

&lt;p&gt;The frontend is a static React app served by Nginx. When your browser makes API calls to &lt;code&gt;/api/*&lt;/code&gt;, we need Nginx to proxy those requests to the backend service.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;argocd/cloudnotes/nginx-configmap.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&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;default.conf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;server {&lt;/span&gt;
        &lt;span class="s"&gt;listen 80;&lt;/span&gt;

        &lt;span class="s"&gt;# Serve static frontend files&lt;/span&gt;
        &lt;span class="s"&gt;location / {&lt;/span&gt;
            &lt;span class="s"&gt;root /usr/share/nginx/html;&lt;/span&gt;
            &lt;span class="s"&gt;index index.html index.htm;&lt;/span&gt;
            &lt;span class="s"&gt;try_files $uri $uri/ /index.html;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;

        &lt;span class="s"&gt;# Proxy API requests to backend service&lt;/span&gt;
        &lt;span class="s"&gt;location /api/ {&lt;/span&gt;
            &lt;span class="s"&gt;proxy_pass http://cloudnotes-backend:5000/api/;&lt;/span&gt;
            &lt;span class="s"&gt;proxy_http_version 1.1;&lt;/span&gt;
            &lt;span class="s"&gt;proxy_set_header Host $host;&lt;/span&gt;
            &lt;span class="s"&gt;proxy_set_header X-Real-IP $remote_addr;&lt;/span&gt;
            &lt;span class="s"&gt;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;&lt;/span&gt;
            &lt;span class="s"&gt;proxy_set_header X-Forwarded-Proto $scheme;&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why is this needed?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The frontend runs in your &lt;strong&gt;browser&lt;/strong&gt; (external)&lt;/li&gt;
&lt;li&gt;The backend is &lt;strong&gt;ClusterIP&lt;/strong&gt; (internal only)&lt;/li&gt;
&lt;li&gt;Without the proxy, API calls from the browser would fail&lt;/li&gt;
&lt;li&gt;Nginx acts as a reverse proxy, forwarding &lt;code&gt;/api/*&lt;/code&gt; requests to the backend&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Frontend Deployment
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;argocd/cloudnotes/frontend-deployment.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes-frontend&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
        &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&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;frontend&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;bansikah/cloudnotes-frontend:latest&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;50m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;64Mi"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;200m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
        &lt;span class="na"&gt;readinessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;initialDelaySeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
          &lt;span class="na"&gt;periodSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
        &lt;span class="na"&gt;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;httpGet&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;initialDelaySeconds&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;periodSeconds&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;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config&lt;/span&gt;
          &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/http.d/default.conf&lt;/span&gt;
          &lt;span class="na"&gt;subPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default.conf&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config&lt;/span&gt;
        &lt;span class="na"&gt;configMap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; The &lt;code&gt;volumeMounts&lt;/code&gt; section mounts our custom Nginx config, enabling the API proxy to the backend.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Frontend Service
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;argocd/cloudnotes/frontend-service.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes-frontend&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Creating an Argo CD Application
&lt;/h2&gt;

&lt;p&gt;Now we create the Argo CD Application resource that tells Argo CD what to deploy and where.&lt;/p&gt;

&lt;h3&gt;
  
  
  Understanding the Application CRD
&lt;/h3&gt;

&lt;p&gt;An Argo CD &lt;strong&gt;Application&lt;/strong&gt; is a Custom Resource that defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Source&lt;/strong&gt;: Where to get the manifests (Git repo, path, branch)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Destination&lt;/strong&gt;: Where to deploy (cluster, namespace)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync Policy&lt;/strong&gt;: How to handle updates (manual/automatic)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The Application Manifest
&lt;/h3&gt;

&lt;p&gt;Create &lt;code&gt;argocd/applications/cloudnotes.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/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;Application&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;cloudnotes&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
  &lt;span class="na"&gt;finalizers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;resources-finalizer.argocd.argoproj.io&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;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;

  &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://github.com/bansikah22/eks-at-scale.git&lt;/span&gt;
    &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd/cloudnotes&lt;/span&gt;

  &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cloudnotes&lt;/span&gt;

  &lt;span class="na"&gt;syncPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;automated&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;prune&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;selfHeal&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;allowEmpty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;syncOptions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;CreateNamespace=true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PrunePropagationPolicy=foreground&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;PruneLast=true&lt;/span&gt;
    &lt;span class="na"&gt;retry&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
      &lt;span class="na"&gt;backoff&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
        &lt;span class="na"&gt;factor&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;maxDuration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3m&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breaking Down the Application
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metadata.name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name of the application in Argo CD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metadata.namespace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Must be &lt;code&gt;argocd&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spec.project&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Argo CD project (use &lt;code&gt;default&lt;/code&gt; for now)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source.repoURL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Git repository URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source.targetRevision&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Branch, tag, or commit to track&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;source.path&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Path to manifests within the repo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;destination.server&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target cluster (in-cluster uses this URL)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;destination.namespace&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Target namespace for resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;syncPolicy.automated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enable automatic sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;syncPolicy.automated.prune&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Delete resources removed from Git&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;syncPolicy.automated.selfHeal&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Revert manual changes to cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Deploying via GitOps
&lt;/h2&gt;

&lt;p&gt;Now let's see GitOps in action!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Commit and Push the Manifests
&lt;/h3&gt;

&lt;p&gt;First, ensure all your manifests are committed to Git:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/eks-at-scale
git add argocd/
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Add CloudNotes GitOps manifests"&lt;/span&gt;
git push origin master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Apply the Argo CD Application
&lt;/h3&gt;



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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;application.argoproj.io/cloudnotes created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Watch Argo CD Sync
&lt;/h3&gt;

&lt;p&gt;Check the application status:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get applications &lt;span class="nt"&gt;-n&lt;/span&gt; argocd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME         SYNC STATUS   HEALTH STATUS
cloudnotes   Synced        Healthy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the Argo CD CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;argocd app get cloudnotes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Step 4: Observe in the UI
&lt;/h3&gt;

&lt;p&gt;Open the Argo CD UI (&lt;code&gt;https://localhost:8080&lt;/code&gt;) and you'll see:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;cloudnotes&lt;/strong&gt; application card&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync Status&lt;/strong&gt;: Synced (green)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health Status&lt;/strong&gt;: Healthy (green heart)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Click on the application to see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All Kubernetes resources created&lt;/li&gt;
&lt;li&gt;Resource relationships (Deployment -&amp;gt; ReplicaSet -&amp;gt; Pods)&lt;/li&gt;
&lt;li&gt;Real-time sync status&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h3&gt;
  
  
  Step 5: Verify the Deployment
&lt;/h3&gt;

&lt;p&gt;Check that CloudNotes is running:&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="c"&gt;# Check pods&lt;/span&gt;
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; cloudnotes

&lt;span class="c"&gt;# Check services&lt;/span&gt;
kubectl get svc &lt;span class="nt"&gt;-n&lt;/span&gt; cloudnotes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9ce7h4v73hammj2xhup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm9ce7h4v73hammj2xhup.png" alt="Get pods" width="800" height="279"&gt;&lt;/a&gt;&lt;br&gt;
Expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                   READY   STATUS    RESTARTS   AGE
cloudnotes-backend-xxxxx-xxxxx         1/1     Running   0          2m
cloudnotes-backend-xxxxx-xxxxx         1/1     Running   0          2m
cloudnotes-frontend-xxxxx-xxxxx        1/1     Running   0          2m
cloudnotes-frontend-xxxxx-xxxxx        1/1     Running   0          2m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Access the Application
&lt;/h3&gt;

&lt;p&gt;Get the LoadBalancer URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc cloudnotes-frontend &lt;span class="nt"&gt;-n&lt;/span&gt; cloudnotes &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have the EXTERNAL-IP:&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="nv"&gt;LB_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get svc cloudnotes-frontend &lt;span class="nt"&gt;-n&lt;/span&gt; cloudnotes &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.loadBalancer.ingress[0].hostname}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"CloudNotes URL: http://&lt;/span&gt;&lt;span class="nv"&gt;$LB_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the URL in your browser to see the CloudNotes application!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffbp0fklx04tays6x4qpf.png" alt="application" width="800" height="454"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Automated Sync and Self-Healing
&lt;/h2&gt;

&lt;p&gt;This is where GitOps truly shines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automated Sync in Action
&lt;/h3&gt;

&lt;p&gt;Let's make a change to our manifests and watch Argo CD automatically sync.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Update the replica count:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;argocd/cloudnotes/frontend-deployment.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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;  &lt;span class="c1"&gt;# Changed from 2 to 3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Commit and push:&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;git add argocd/cloudnotes/frontend-deployment.yaml
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Scale frontend to 3 replicas"&lt;/span&gt;
git push origin master
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Watch Argo CD sync:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Within 3 minutes (default sync interval), Argo CD will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Detect the Git change&lt;/li&gt;
&lt;li&gt;Compare with cluster state&lt;/li&gt;
&lt;li&gt;Apply the new configuration
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; cloudnotes &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;frontend &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see a third frontend pod being created automatically!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fhrfyejvkif11ujxkyv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6fhrfyejvkif11ujxkyv.png" alt="Syncfrontendpodterminal" width="800" height="79"&gt;&lt;/a&gt;&lt;br&gt;
and on ArgoCD dashboard you will see &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihaq5a5aw9tifmmsf6xc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fihaq5a5aw9tifmmsf6xc.png" alt="Argo sync" width="800" height="437"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Self-Healing in Action
&lt;/h3&gt;

&lt;p&gt;Self-healing reverts unauthorized changes made directly to the cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Manually delete a pod:&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;kubectl delete pod &lt;span class="nt"&gt;-n&lt;/span&gt; cloudnotes &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;frontend &lt;span class="nt"&gt;--wait&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Watch Argo CD restore it:&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;kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; cloudnotes &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;frontend &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within seconds, Argo CD detects the drift and recreates the pod to match the desired state (3 replicas).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Try scaling manually:&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;kubectl scale deployment cloudnotes-frontend &lt;span class="nt"&gt;-n&lt;/span&gt; cloudnotes &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch as Argo CD reverts this change back to 3 replicas (as defined in Git).&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Accidental deletions&lt;/strong&gt; are automatically recovered&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unauthorized changes&lt;/strong&gt; are reverted&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster state&lt;/strong&gt; always matches Git&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No manual intervention&lt;/strong&gt; required&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Observing the Deployment
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Argo CD UI Features
&lt;/h3&gt;

&lt;p&gt;The Argo CD web interface provides:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application View:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Visual resource tree showing all Kubernetes objects&lt;/li&gt;
&lt;li&gt;Health status for each resource&lt;/li&gt;
&lt;li&gt;Sync status (in sync, out of sync, unknown)&lt;/li&gt;
&lt;li&gt;Recent sync history&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Resource Details:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Click any resource to see its YAML&lt;/li&gt;
&lt;li&gt;View events and logs&lt;/li&gt;
&lt;li&gt;See relationships between resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sync Operations:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manual sync button&lt;/li&gt;
&lt;li&gt;Sync with prune option&lt;/li&gt;
&lt;li&gt;Rollback to previous versions&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Useful CLI Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all applications&lt;/span&gt;
argocd app list

&lt;span class="c"&gt;# Get detailed application info&lt;/span&gt;
argocd app get cloudnotes

&lt;span class="c"&gt;# View application history&lt;/span&gt;
argocd app &lt;span class="nb"&gt;history &lt;/span&gt;cloudnotes

&lt;span class="c"&gt;# Manually trigger sync&lt;/span&gt;
argocd app &lt;span class="nb"&gt;sync &lt;/span&gt;cloudnotes

&lt;span class="c"&gt;# View application logs&lt;/span&gt;
argocd app logs cloudnotes

&lt;span class="c"&gt;# Rollback to previous version&lt;/span&gt;
argocd app rollback cloudnotes 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Checking Health Status
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Quick status check&lt;/span&gt;
kubectl get applications &lt;span class="nt"&gt;-n&lt;/span&gt; argocd

&lt;span class="c"&gt;# Detailed health&lt;/span&gt;
argocd app get cloudnotes &lt;span class="nt"&gt;--show-operation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Health statuses:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Healthy&lt;/strong&gt;: All resources are healthy&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Progressing&lt;/strong&gt;: Resources are being updated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Degraded&lt;/strong&gt;: Some resources have issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Suspended&lt;/strong&gt;: Application is paused&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing&lt;/strong&gt;: Resources don't exist yet&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  GitOps Workflow Summary
&lt;/h2&gt;

&lt;p&gt;Here's the complete GitOps workflow we've implemented:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Developer                    Git                      Argo CD                  EKS Cluster
    │                         │                          │                          │
    │  1. Edit manifests      │                          │                          │
    │─────────────────────────►                          │                          │
    │                         │                          │                          │
    │  2. git commit &amp;amp; push   │                          │                          │
    │─────────────────────────►                          │                          │
    │                         │                          │                          │
    │                         │  3. Detect changes       │                          │
    │                         │◄─────────────────────────│                          │
    │                         │                          │                          │
    │                         │                          │  4. Apply manifests      │
    │                         │                          │─────────────────────────►│
    │                         │                          │                          │
    │                         │                          │  5. Report status        │
    │                         │                          │◄─────────────────────────│
    │                         │                          │                          │
    │  6. View in UI/CLI      │                          │                          │
    │◄─────────────────────────────────────────────────────                         │
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Clean Up
&lt;/h2&gt;

&lt;p&gt;When you're done experimenting:&lt;/p&gt;

&lt;h3&gt;
  
  
  Delete the Argo CD Application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# This will delete all CloudNotes resources&lt;/span&gt;
argocd app delete cloudnotes &lt;span class="nt"&gt;--cascade&lt;/span&gt;

&lt;span class="c"&gt;# Or using kubectl&lt;/span&gt;
kubectl delete application cloudnotes &lt;span class="nt"&gt;-n&lt;/span&gt; argocd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Uninstall Argo CD (Optional)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete &lt;span class="nt"&gt;-n&lt;/span&gt; argocd &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml
kubectl delete namespace argocd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Delete the EKS Cluster
&lt;/h3&gt;

&lt;p&gt;If you're done with the entire series:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitOps Fundamentals&lt;/strong&gt; - Git as the single source of truth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why GitOps Matters&lt;/strong&gt; - Auditability, rollbacks, collaboration, disaster recovery&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Argo CD&lt;/strong&gt; - A powerful GitOps tool for Kubernetes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Installation&lt;/strong&gt; - Setting up Argo CD on EKS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Deployment&lt;/strong&gt; - Deploying CloudNotes via GitOps&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated Sync&lt;/strong&gt; - Changes in Git automatically applied&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-Healing&lt;/strong&gt; - Drift detection and automatic correction&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Git is the source of truth&lt;/strong&gt; - All desired state lives in version control&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Argo CD automates deployments&lt;/strong&gt; - No more manual &lt;code&gt;kubectl apply&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Changes are auditable&lt;/strong&gt; - Every deployment is a Git commit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-healing ensures consistency&lt;/strong&gt; - The cluster always matches Git&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rollbacks are simple&lt;/strong&gt; - Just revert the Git commit&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team collaboration improves&lt;/strong&gt; - Use PRs for deployment reviews&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;Congratulations! You've completed the &lt;strong&gt;Amazon EKS at Scale&lt;/strong&gt; series!&lt;/p&gt;

&lt;p&gt;Over five articles, you've learned:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Part&lt;/th&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Key Skills&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Introduction to EKS&lt;/td&gt;
&lt;td&gt;Understanding managed Kubernetes, worker nodes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;EKS Architecture&lt;/td&gt;
&lt;td&gt;Control plane, networking, IAM integration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Provisioning with Terraform&lt;/td&gt;
&lt;td&gt;Infrastructure as Code, VPC, EKS modules&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Deploying Applications&lt;/td&gt;
&lt;td&gt;Deployments, Services, LoadBalancers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;GitOps with Argo CD&lt;/td&gt;
&lt;td&gt;Declarative deployments, automated sync, self-healing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;You now have a complete, production-ready workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Infrastructure (Terraform) → Cluster (EKS) → Applications (GitOps/Argo CD)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Here are some areas to explore further:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secrets Management&lt;/strong&gt; - HashiCorp Vault, AWS Secrets Manager, Sealed Secrets&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD Integration&lt;/strong&gt; - GitHub Actions, Jenkins, GitLab CI with Argo CD&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Cluster&lt;/strong&gt; - Argo CD managing multiple EKS clusters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Advanced Argo CD&lt;/strong&gt; - ApplicationSets, App of Apps pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitoring&lt;/strong&gt; - Prometheus, Grafana, CloudWatch Container Insights&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Mesh&lt;/strong&gt; - Istio, AWS App Mesh for advanced networking&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://argo-cd.readthedocs.io/" rel="noopener noreferrer"&gt;Argo CD Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://opengitops.dev/" rel="noopener noreferrer"&gt;GitOps Principles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/bansikah22/cloudnotes" rel="noopener noreferrer"&gt;CloudNotes Repository&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/" rel="noopener noreferrer"&gt;Amazon EKS Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/" rel="noopener noreferrer"&gt;Kubernetes Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Thank You!
&lt;/h2&gt;

&lt;p&gt;Thank you for following along with this series! &lt;/p&gt;

&lt;p&gt;If you found it helpful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Star the &lt;a href="https://github.com/bansikah22/eks-at-scale" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Share with others learning Kubernetes&lt;/li&gt;
&lt;li&gt;Drop your questions and feedback in the comments&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Happy deploying!&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This concludes the Amazon EKS at Scale series. Stay tuned for more DevOps and cloud-native content!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>argocd</category>
    </item>
    <item>
      <title>Amazon EKS Series - Part 4: Deploying Applications on Amazon EKS</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Wed, 17 Dec 2025 15:50:54 +0000</pubDate>
      <link>https://dev.to/bansikah/amazon-eks-series-part-4-deploying-applications-on-amazon-eks-9f6</link>
      <guid>https://dev.to/bansikah/amazon-eks-series-part-4-deploying-applications-on-amazon-eks-9f6</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome back to the &lt;strong&gt;Amazon EKS at Scale&lt;/strong&gt; series!&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/bansikah/amazon-eks-series-part-3-provisioning-an-eks-cluster-with-terraform-413c"&gt;Part 3&lt;/a&gt;, we provisioned a production-ready EKS cluster using Terraform and community modules. Now it's time to put that cluster to work!&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploy a simple application using Kubernetes&lt;/li&gt;
&lt;li&gt;Understand Deployments and Pods&lt;/li&gt;
&lt;li&gt;Expose applications with Services&lt;/li&gt;
&lt;li&gt;Use AWS Load Balancers with EKS&lt;/li&gt;
&lt;li&gt;Get started with Helm for package management&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; Ensure your EKS cluster from Part 3 is running and &lt;code&gt;kubectl&lt;/code&gt; is configured.&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="c"&gt;# Verify cluster connection&lt;/span&gt;
kubectl get nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your worker nodes in &lt;code&gt;Ready&lt;/code&gt; status.&lt;/p&gt;




&lt;h2&gt;
  
  
  Choosing a Sample Application
&lt;/h2&gt;

&lt;p&gt;When learning Kubernetes, it's best to start with simple applications. This lets you focus on &lt;strong&gt;Kubernetes concepts&lt;/strong&gt; rather than application complexity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Simple Applications?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fewer moving parts&lt;/strong&gt; — Easier to debug&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick feedback&lt;/strong&gt; — Fast deployment cycles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus on fundamentals&lt;/strong&gt; — Learn the platform, not the app&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Our Choice: NGINX
&lt;/h3&gt;

&lt;p&gt;We'll deploy &lt;strong&gt;NGINX&lt;/strong&gt; — a lightweight web server that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Starts quickly&lt;/li&gt;
&lt;li&gt;Has a small image size&lt;/li&gt;
&lt;li&gt;Returns a simple webpage (easy to verify)&lt;/li&gt;
&lt;li&gt;Is widely used in examples and documentation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Later, we'll also explore deploying with Helm using a different application.&lt;/p&gt;




&lt;h2&gt;
  
  
  Understanding Kubernetes Deployments
&lt;/h2&gt;

&lt;p&gt;Before we deploy anything, let's understand what a &lt;strong&gt;Deployment&lt;/strong&gt; is.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a Deployment?
&lt;/h3&gt;

&lt;p&gt;A Deployment is a Kubernetes resource that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Declares the desired state&lt;/strong&gt; of your application&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manages Pods&lt;/strong&gt; (the smallest deployable units)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles updates&lt;/strong&gt; with rolling deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensures availability&lt;/strong&gt; by maintaining replica counts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as telling Kubernetes: &lt;em&gt;"I want 3 copies of this application running at all times."&lt;/em&gt; Kubernetes then makes it happen and keeps it that way.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a Pod?
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Pod&lt;/strong&gt; is the smallest unit in Kubernetes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Contains one or more containers&lt;/li&gt;
&lt;li&gt;Shares networking and storage&lt;/li&gt;
&lt;li&gt;Has a unique IP address within the cluster&lt;/li&gt;
&lt;li&gt;Is ephemeral (can be replaced at any time)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You rarely create Pods directly. Instead, you create Deployments that manage Pods for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deployment YAML Structure
&lt;/h3&gt;

&lt;p&gt;Here's what a minimal Deployment looks like:&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;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.25&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breaking Down the YAML
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;apiVersion&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;API version for this resource type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kind&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Type of resource (Deployment)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;metadata.name&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name of the Deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spec.replicas&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of Pod copies to run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spec.selector&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;How the Deployment finds its Pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;spec.template&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Template for creating Pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;containers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List of containers in each Pod&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Docker image to use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;containerPort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Port the container listens on&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;New to kubernetes? you can read my &lt;a href="https://dev.to/bansikah/kubernetes-for-beginners-69h"&gt;Kubernetes for beginners&lt;/a&gt; post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying Our First Application
&lt;/h2&gt;

&lt;p&gt;Let's deploy NGINX to our EKS cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Deployment
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;nginx-deployment.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-deployment&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx:1.25&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; We've added resource requests and limits. This is a best practice that helps Kubernetes schedule Pods efficiently and prevents any single Pod from consuming too many resources.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Step 2: Apply the Deployment
&lt;/h3&gt;



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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deployment.apps/nginx-deployment created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Verify the Deployment
&lt;/h3&gt;

&lt;p&gt;Check the Deployment status:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME               READY   UP-TO-DATE   AVAILABLE   AGE
nginx-deployment   3/3     3            3           30s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the Pods:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-6d6565499c-2xjkl   1/1     Running   0          45s
nginx-deployment-6d6565499c-8mzft   1/1     Running   0          45s
nginx-deployment-6d6565499c-vwxyz   1/1     Running   0          45s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All 3 replicas are running!&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: View More Details
&lt;/h3&gt;

&lt;p&gt;For detailed information about a Pod:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;To see which nodes the Pods are running on:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Understanding Kubernetes Services
&lt;/h2&gt;

&lt;p&gt;Our NGINX Pods are running, but how do we access them?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pods have IP addresses, but they're &lt;strong&gt;internal&lt;/strong&gt; to the cluster&lt;/li&gt;
&lt;li&gt;Pods are &lt;strong&gt;ephemeral&lt;/strong&gt; — they can be replaced at any time&lt;/li&gt;
&lt;li&gt;Each Pod has a &lt;strong&gt;different IP&lt;/strong&gt; address&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you try to access a Pod directly by IP, what happens when that Pod dies and a new one takes its place with a different IP?&lt;/p&gt;

&lt;h3&gt;
  
  
  The Solution: Services
&lt;/h3&gt;

&lt;p&gt;A &lt;strong&gt;Service&lt;/strong&gt; provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stable endpoint&lt;/strong&gt; — A single IP/DNS name that doesn't change&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load balancing&lt;/strong&gt; — Distributes traffic across all matching Pods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service discovery&lt;/strong&gt; — Other apps can find your service by name&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Service Types
&lt;/h3&gt;

&lt;p&gt;Kubernetes offers several Service types:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Use Case&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ClusterIP&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Internal IP only (default)&lt;/td&gt;
&lt;td&gt;Internal communication between services&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;NodePort&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Exposes on each node's IP at a static port&lt;/td&gt;
&lt;td&gt;Development, testing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;LoadBalancer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Provisions an external load balancer&lt;/td&gt;
&lt;td&gt;Production traffic from internet&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  ClusterIP (Default)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Only accessible &lt;strong&gt;within&lt;/strong&gt; the cluster&lt;/li&gt;
&lt;li&gt;Great for internal services (databases, caches, etc.)&lt;/li&gt;
&lt;li&gt;Other Pods can access via &lt;code&gt;service-name.namespace.svc.cluster.local&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Exposes the service on each node's IP at a specific port (30000-32767)&lt;/li&gt;
&lt;li&gt;Accessible from outside the cluster via &lt;code&gt;&amp;lt;NodeIP&amp;gt;:&amp;lt;NodePort&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Not recommended for production (no load balancing across nodes)&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Best for production workloads on AWS&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Automatically provisions an AWS Elastic Load Balancer (ELB)&lt;/li&gt;
&lt;li&gt;Provides a public DNS name&lt;/li&gt;
&lt;li&gt;Handles SSL termination (with proper configuration)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Exposing Our Application with LoadBalancer
&lt;/h2&gt;

&lt;p&gt;Let's create a LoadBalancer Service to expose our NGINX deployment to the internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Service
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;nginx-service.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-service&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Understanding the Service YAML
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type: LoadBalancer&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tells Kubernetes to provision an external load balancer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;selector.app: nginx&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Routes traffic to Pods with label &lt;code&gt;app: nginx&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;port: 80&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Port the Service listens on&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;targetPort: 80&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Port on the Pods to forward traffic to&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Step 2: Apply the Service
&lt;/h3&gt;



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

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Watch the Service Status
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get service nginx-service &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initially, you'll see &lt;code&gt;&amp;lt;pending&amp;gt;&lt;/code&gt; for the external IP:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME            TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
nginx-service   LoadBalancer   172.20.10.123   &amp;lt;pending&amp;gt;     80:31234/TCP   10s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After 1-2 minutes, AWS provisions the load balancer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME            TYPE           CLUSTER-IP      EXTERNAL-IP                                                              PORT(S)        AGE
nginx-service   LoadBalancer   172.20.10.123   a1b2c3d4e5f6g7h8i9.us-east-1.elb.amazonaws.com   80:31234/TCP   90s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press &lt;code&gt;Ctrl+C&lt;/code&gt; to stop watching.&lt;/p&gt;

&lt;h3&gt;
  
  
  What Happened Behind the Scenes?
&lt;/h3&gt;

&lt;p&gt;When you created the LoadBalancer Service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes&lt;/strong&gt; detected the Service type and called the AWS cloud controller&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS&lt;/strong&gt; provisioned a Classic Load Balancer (or NLB with annotations)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ELB&lt;/strong&gt; was configured to forward traffic to your worker nodes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;kube-proxy&lt;/strong&gt; on each node routes traffic to the correct Pods&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This integration is one of the powerful benefits of running Kubernetes on AWS!&lt;/p&gt;




&lt;h2&gt;
  
  
  Verifying the Application
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Get the Load Balancer URL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get service nginx-service &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.loadBalancer.ingress[0].hostname}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or simply:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Access via Browser
&lt;/h3&gt;

&lt;p&gt;Open the EXTERNAL-IP URL in your browser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;http://a1b2c3d4e5f6g7h8i9.us-east-1.elb.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see the default NGINX welcome page!&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Access via curl
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Store the LoadBalancer URL in a variable and curl it&lt;/span&gt;
&lt;span class="nv"&gt;LB_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get svc nginx-service &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.loadBalancer.ingress[0].hostname}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl http://&lt;span class="nv"&gt;$LB_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Welcome to nginx!&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;
...
&lt;span class="nt"&gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h3&gt;
  
  
  Check Pod Logs
&lt;/h3&gt;

&lt;p&gt;View the access logs from NGINX:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl logs &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see HTTP request logs from your browser/curl requests.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scaling the Application
&lt;/h2&gt;

&lt;p&gt;One of Kubernetes' strengths is easy scaling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scale Up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl scale deployment nginx-deployment &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;You now have 5 NGINX Pods!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmowuapzcdamup5fh48t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffmowuapzcdamup5fh48t.png" alt="Scale pods" width="800" height="132"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Scale Down
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl scale deployment nginx-deployment &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Kubernetes will gracefully terminate 3 Pods.&lt;/p&gt;
&lt;h3&gt;
  
  
  Autoscaling (Preview)
&lt;/h3&gt;

&lt;p&gt;For automatic scaling based on CPU/memory, you can use the Horizontal Pod Autoscaler (HPA). We'll cover this in a future article.&lt;/p&gt;


&lt;h2&gt;
  
  
  Introduction to Helm
&lt;/h2&gt;

&lt;p&gt;So far, we've been writing YAML files manually. This works, but what happens when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need to deploy to multiple environments?&lt;/li&gt;
&lt;li&gt;You want to share your deployment with others?&lt;/li&gt;
&lt;li&gt;Applications require dozens of YAML files?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  What is Helm?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Helm&lt;/strong&gt; is the package manager for Kubernetes. Think of it like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apt&lt;/code&gt; for Ubuntu&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;brew&lt;/code&gt; for macOS&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;npm&lt;/code&gt; for Node.js&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Key Concepts
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concept&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Chart&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A package containing all Kubernetes manifests for an application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Release&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A deployed instance of a chart&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Repository&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;A collection of charts (like a package registry)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Values&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Configuration options to customize a chart&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Why Use Helm?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Templating&lt;/strong&gt; — Use variables instead of hardcoded values&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusability&lt;/strong&gt; — Package once, deploy many times&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning&lt;/strong&gt; — Track and rollback releases&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community&lt;/strong&gt; — Thousands of pre-built charts available&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplicity&lt;/strong&gt; — Deploy complex apps with one command&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Helm vs Raw YAML
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;Raw YAML&lt;/th&gt;
&lt;th&gt;Helm&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Learning curve&lt;/td&gt;
&lt;td&gt;Lower&lt;/td&gt;
&lt;td&gt;Slightly higher&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexibility&lt;/td&gt;
&lt;td&gt;Full control&lt;/td&gt;
&lt;td&gt;Template-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reusability&lt;/td&gt;
&lt;td&gt;Copy-paste&lt;/td&gt;
&lt;td&gt;Charts and values&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Complex apps&lt;/td&gt;
&lt;td&gt;Many files to manage&lt;/td&gt;
&lt;td&gt;Single command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment config&lt;/td&gt;
&lt;td&gt;Manual changes&lt;/td&gt;
&lt;td&gt;Values files&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;


&lt;h2&gt;
  
  
  Installing Helm
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Install Helm CLI
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux&lt;/span&gt;
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;helm

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
helm version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Helm Basics
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Add a chart repository (example with prometheus-community)&lt;/span&gt;
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

&lt;span class="c"&gt;# Update repository information&lt;/span&gt;
helm repo update

&lt;span class="c"&gt;# Search for charts&lt;/span&gt;
helm search repo prometheus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Many popular Helm chart repositories like Bitnami have moved to enterprise licensing models. When choosing charts, prefer community-maintained repositories or official project charts. Always check the license and image sources before using a chart in production.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  Deploying Apache (Manual Approach)
&lt;/h2&gt;

&lt;p&gt;While Helm is powerful, sometimes the simplest approach is best. Let's deploy Apache using the official Docker Hub image with manual manifests - similar to how we deployed NGINX.&lt;/p&gt;
&lt;h3&gt;
  
  
  Why Manual Deployment?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No licensing concerns&lt;/strong&gt; - Uses official Docker Hub images&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full control&lt;/strong&gt; - You understand exactly what's deployed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simpler debugging&lt;/strong&gt; - Fewer abstractions to troubleshoot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning value&lt;/strong&gt; - Reinforces Kubernetes concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 1: Create the Apache Deployment
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;apache-deployment.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache-deployment&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache&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;apache&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;httpd:2.4&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
        &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;100m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;128Mi"&lt;/span&gt;
          &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;250m"&lt;/span&gt;
            &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;256Mi"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;httpd:2.4&lt;/code&gt; image is the official Apache HTTP Server image from Docker Hub.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create the Apache Service
&lt;/h3&gt;

&lt;p&gt;Create a file named &lt;code&gt;apache-service.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;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache-service&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;LoadBalancer&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apache&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Deploy Apache
&lt;/h3&gt;



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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;deployment.apps/apache-deployment created
service/apache-service created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Verify the Deployment
&lt;/h3&gt;

&lt;p&gt;Check the pods:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pods &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;apache
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                               READY   STATUS    RESTARTS   AGE
apache-deployment-6c57ff8d-cdfzr   1/1     Running   0          75s
apache-deployment-6c57ff8d-nbdjq   1/1     Running   0          75s
apache-deployment-6c57ff8d-xqv5f   1/1     Running   0          75s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Get the LoadBalancer URL
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc apache-service &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the EXTERNAL-IP to be assigned (1-2 minutes):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME             TYPE           CLUSTER-IP      EXTERNAL-IP                                                              PORT(S)        AGE
apache-service   LoadBalancer   172.20.86.183   a4108c17eb1b441978cb345f8622d540-727057055.us-east-1.elb.amazonaws.com   80:31783/TCP   60s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Test the Application
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; AWS ELB DNS propagation can take 2-5 minutes. If you get a DNS resolution error, wait a moment and try again.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;LB_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get svc apache-service &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{.status.loadBalancer.ingress[0].hostname}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl http://&lt;span class="nv"&gt;$LB_URL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;&amp;lt;h1&amp;gt;&lt;/span&gt;It works!&lt;span class="nt"&gt;&amp;lt;/h1&amp;gt;&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or YOu can check on the browser :&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mkdd4vzou9s9h75qvjv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5mkdd4vzou9s9h75qvjv.png" alt="Apache browser" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You now have two applications running on your EKS cluster - NGINX and Apache!&lt;/p&gt;


&lt;h2&gt;
  
  
  When to Use Helm
&lt;/h2&gt;

&lt;p&gt;Helm shines for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Complex applications&lt;/strong&gt; with many interdependent resources&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community charts&lt;/strong&gt; from trusted sources (check licenses!)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment-specific configurations&lt;/strong&gt; using values files&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Release management&lt;/strong&gt; with rollback capabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For simple deployments like web servers, manual manifests are often clearer and easier to maintain.&lt;/p&gt;
&lt;h3&gt;
  
  
  Example: Using Helm with Community Charts
&lt;/h3&gt;

&lt;p&gt;Here's how you might use Helm with a community-maintained chart:&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="c"&gt;# Add a repository&lt;/span&gt;
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts

&lt;span class="c"&gt;# Install with custom values&lt;/span&gt;
helm &lt;span class="nb"&gt;install &lt;/span&gt;my-prometheus prometheus-community/prometheus &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; server.persistentVolume.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;

&lt;span class="c"&gt;# List releases&lt;/span&gt;
helm list

&lt;span class="c"&gt;# Upgrade a release&lt;/span&gt;
helm upgrade my-prometheus prometheus-community/prometheus &lt;span class="nt"&gt;--set&lt;/span&gt; server.replicas&lt;span class="o"&gt;=&lt;/span&gt;2

&lt;span class="c"&gt;# View history&lt;/span&gt;
helm &lt;span class="nb"&gt;history &lt;/span&gt;my-prometheus

&lt;span class="c"&gt;# Rollback if needed&lt;/span&gt;
helm rollback my-prometheus 1

&lt;span class="c"&gt;# Uninstall&lt;/span&gt;
helm uninstall my-prometheus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;For reference, here's the complete file structure for this article:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;k8s-manifests/
├── README.md                    # Usage instructions
├── nginx-deployment.yaml        # NGINX Deployment manifest
├── nginx-service.yaml           # NGINX LoadBalancer Service 
├── apache-deployment.yaml       # Apache Deployment manifest
└── apache-service.yaml          # Apache LoadBalancer Service 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All files are available in the series repository.&lt;/p&gt;




&lt;h2&gt;
  
  
  Clean Up
&lt;/h2&gt;

&lt;p&gt;Let's clean up the resources we created:&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="c"&gt;# Navigate to the k8s-manifests directory&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;k8s-manifests

&lt;span class="c"&gt;# Delete NGINX deployment and service&lt;/span&gt;
kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-service.yaml
kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; nginx-deployment.yaml

&lt;span class="c"&gt;# Delete Apache deployment and service&lt;/span&gt;
kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; apache-service.yaml
kubectl delete &lt;span class="nt"&gt;-f&lt;/span&gt; apache-deployment.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify everything is removed:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes Deployments&lt;/strong&gt; — Declare and manage your application Pods&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pods&lt;/strong&gt; — The smallest deployable units in Kubernetes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Services&lt;/strong&gt; — Provide stable endpoints and load balancing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LoadBalancer Services&lt;/strong&gt; — Automatically provision AWS ELBs for external access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling&lt;/strong&gt; — Easily scale applications up or down&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helm&lt;/strong&gt; — Package manager for Kubernetes that simplifies deployments&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;Deployments manage Pods and ensure your desired state is maintained&lt;/li&gt;
&lt;li&gt;Services provide stable networking for ephemeral Pods&lt;/li&gt;
&lt;li&gt;LoadBalancer Services integrate with AWS to provision ELBs automatically&lt;/li&gt;
&lt;li&gt;Helm simplifies deploying complex applications with a single command&lt;/li&gt;
&lt;li&gt;Always use resource requests and limits for production workloads&lt;/li&gt;
&lt;li&gt;Labels are crucial for connecting Deployments and Services&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Any doubts in the code you can always refer to the github repo &lt;a href="https://github.com/bansikah22/eks-at-scale/tree/master/k8s-manifests" rel="noopener noreferrer"&gt;Manifest files&lt;/a&gt;&lt;/p&gt;




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

&lt;p&gt;In &lt;strong&gt;Part 5&lt;/strong&gt;, we'll introduce &lt;strong&gt;GitOps&lt;/strong&gt; and manage our deployments using &lt;strong&gt;Argo CD&lt;/strong&gt;. You'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What GitOps is and why it matters&lt;/li&gt;
&lt;li&gt;Setting up Argo CD on EKS&lt;/li&gt;
&lt;li&gt;Deploying applications from Git repositories&lt;/li&gt;
&lt;li&gt;Automated sync and self-healing deployments&lt;/li&gt;
&lt;/ul&gt;

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




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;Kubernetes Deployments Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/service/" rel="noopener noreferrer"&gt;Kubernetes Services Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://helm.sh/docs/" rel="noopener noreferrer"&gt;Helm Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/load-balancing.html" rel="noopener noreferrer"&gt;Amazon EKS Load Balancing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Found this article helpful? Follow along with the series and drop your questions in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>eks</category>
      <category>devops</category>
    </item>
    <item>
      <title>Amazon EKS Series - Part 3: Provisioning an EKS Cluster with Terraform</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Wed, 17 Dec 2025 14:33:53 +0000</pubDate>
      <link>https://dev.to/bansikah/amazon-eks-series-part-3-provisioning-an-eks-cluster-with-terraform-413c</link>
      <guid>https://dev.to/bansikah/amazon-eks-series-part-3-provisioning-an-eks-cluster-with-terraform-413c</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome back to the &lt;strong&gt;Amazon EKS at Scale&lt;/strong&gt; series!&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/bansikah/amazon-eks-series-part-2-eks-architecture-and-core-components-36gk"&gt;Part 2&lt;/a&gt;, we explored the architecture of Amazon EKS — the control plane, worker nodes, networking, and IAM integration. Now it's time to put that knowledge into practice.&lt;/p&gt;

&lt;p&gt;In this article, you'll learn how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up a Terraform project for EKS&lt;/li&gt;
&lt;li&gt;Use community modules for VPC and EKS&lt;/li&gt;
&lt;li&gt;Provision a production-ready EKS cluster&lt;/li&gt;
&lt;li&gt;Verify your cluster with kubectl&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What we're building:&lt;/strong&gt; A fully functional EKS cluster with a VPC, private subnets, and managed node groups — all defined as code.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Terraform for EKS?
&lt;/h2&gt;

&lt;p&gt;While &lt;code&gt;eksctl&lt;/code&gt; is great for quick setups (as we saw in Part 1), production environments demand more control and repeatability. That's where &lt;strong&gt;Terraform&lt;/strong&gt; shines.&lt;/p&gt;

&lt;h3&gt;
  
  
  Benefits of Infrastructure as Code (IaC)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reproducibility&lt;/strong&gt; — Create identical clusters across dev, staging, and production environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Version Control&lt;/strong&gt; — Track all infrastructure changes in Git with full history&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Team Collaboration&lt;/strong&gt; — Review infrastructure changes through pull requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automation&lt;/strong&gt; — Integrate with CI/CD pipelines for automated deployments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documentation&lt;/strong&gt; — Your code serves as living documentation of your infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Why Community Modules?
&lt;/h3&gt;

&lt;p&gt;We'll use the official Terraform AWS modules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;terraform-aws-modules/vpc/aws&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;terraform-aws-modules/eks/aws&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These modules are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Production-tested&lt;/strong&gt; — Used by thousands of organizations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Actively maintained&lt;/strong&gt; — Regular updates and security patches&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Well-documented&lt;/strong&gt; — Comprehensive examples and documentation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interview-friendly&lt;/strong&gt; — Widely recognized in the industry&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Tools &amp;amp; Prerequisites
&lt;/h2&gt;

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

&lt;h3&gt;
  
  
  Required Tools
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AWS CLI&lt;/td&gt;
&lt;td&gt;v2.x&lt;/td&gt;
&lt;td&gt;AWS authentication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;&amp;gt;= 1.0&lt;/td&gt;
&lt;td&gt;Infrastructure provisioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kubectl&lt;/td&gt;
&lt;td&gt;Latest&lt;/td&gt;
&lt;td&gt;Kubernetes cluster interaction&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Installing the Tools
&lt;/h3&gt;

&lt;h4&gt;
  
  
  AWS CLI
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux&lt;/span&gt;
curl &lt;span class="s2"&gt;"https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"awscliv2.zip"&lt;/span&gt;
unzip awscliv2.zip
&lt;span class="nb"&gt;sudo&lt;/span&gt; ./aws/install

&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;awscli

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
aws &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Terraform
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux (Ubuntu/Debian)&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt-get &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; gnupg software-properties-common
wget &lt;span class="nt"&gt;-O-&lt;/span&gt; https://apt.releases.hashicorp.com/gpg | gpg &lt;span class="nt"&gt;--dearmor&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /usr/share/keyrings/hashicorp-archive-keyring.gpg
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;lsb_release &lt;span class="nt"&gt;-cs&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; main"&lt;/span&gt; | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/hashicorp.list
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;terraform

&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew tap hashicorp/tap
brew &lt;span class="nb"&gt;install &lt;/span&gt;hashicorp/tap/terraform

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
terraform &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  kubectl
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Linux&lt;/span&gt;
curl &lt;span class="nt"&gt;-LO&lt;/span&gt; &lt;span class="s2"&gt;"https://dl.k8s.io/release/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; https://dl.k8s.io/release/stable.txt&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/bin/linux/amd64/kubectl"&lt;/span&gt;
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x kubectl
&lt;span class="nb"&gt;sudo mv &lt;/span&gt;kubectl /usr/local/bin/

&lt;span class="c"&gt;# macOS&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;kubectl

&lt;span class="c"&gt;# Verify installation&lt;/span&gt;
kubectl version &lt;span class="nt"&gt;--client&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  AWS Credentials
&lt;/h3&gt;

&lt;p&gt;Configure your AWS credentials using one of these methods:&lt;/p&gt;

&lt;h4&gt;
  
  
  Option 1: AWS Configure (Recommended for beginners)
&lt;/h4&gt;



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

&lt;/div&gt;



&lt;p&gt;You'll be prompted for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS Access Key ID&lt;/li&gt;
&lt;li&gt;AWS Secret Access Key&lt;/li&gt;
&lt;li&gt;Default region (e.g., &lt;code&gt;us-east-1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Default output format (e.g., &lt;code&gt;json&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Option 2: Environment Variables
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-access-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-secret-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"us-east-1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Verify AWS Configuration
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Verify AWS CLI is configured&lt;/span&gt;
aws sts get-caller-identity
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see your AWS account ID and user/role information:&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;"UserId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AIDAXXXXXXXXXXXXXXXXX"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Account"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"123456789012"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Arn"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::123456789012:user/your-username"&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;h3&gt;
  
  
  IAM Permissions Required
&lt;/h3&gt;

&lt;p&gt;Your AWS user/role needs permissions to create:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC and networking resources (subnets, NAT gateway, etc.)&lt;/li&gt;
&lt;li&gt;EKS clusters and node groups&lt;/li&gt;
&lt;li&gt;IAM roles and policies&lt;/li&gt;
&lt;li&gt;EC2 instances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For learning purposes, you can use the &lt;code&gt;AdministratorAccess&lt;/code&gt; policy. For production, follow the principle of least privilege.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;A clean project structure makes your Terraform code maintainable. Here's what we'll create or you can visit this repository to see the code &lt;a href="https://github.com/bansikah22/eks-at-scale" rel="noopener noreferrer"&gt;EKS terraform code&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eks-terraform/
├── main.tf          # Main resources (VPC &amp;amp; EKS modules)
├── providers.tf     # Provider configuration
├── versions.tf      # Terraform and provider versions
├── variables.tf     # Input variables
└── outputs.tf       # Output values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Structure?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;versions.tf&lt;/code&gt;&lt;/strong&gt; — Pin Terraform and provider versions for consistency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;providers.tf&lt;/code&gt;&lt;/strong&gt; — Configure AWS provider settings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;variables.tf&lt;/code&gt;&lt;/strong&gt; — Define configurable inputs (region, cluster name, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;main.tf&lt;/code&gt;&lt;/strong&gt; — The core infrastructure definitions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;outputs.tf&lt;/code&gt;&lt;/strong&gt; — Expose useful values after deployment&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This separation keeps your code organized and easy to navigate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Terraform Versions
&lt;/h2&gt;

&lt;p&gt;First, let's pin our Terraform and provider versions. This ensures consistent behavior across different machines and CI/CD pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;versions.tf&lt;/code&gt;&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="nx"&gt;terraform&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.0"&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;aws&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/aws"&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; 5.0"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why version pinning matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prevents unexpected breaking changes&lt;/li&gt;
&lt;li&gt;Ensures team members use compatible versions&lt;/li&gt;
&lt;li&gt;Makes builds reproducible over time&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 2: Provider Configuration
&lt;/h2&gt;

&lt;p&gt;Configure the AWS provider with the region variable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;providers.tf&lt;/code&gt;&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="nx"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;"aws"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;region&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;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Simple and clean — the region comes from our variables file.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Input Variables
&lt;/h2&gt;

&lt;p&gt;Define the configurable parameters for your infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;variables.tf&lt;/code&gt;&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="nx"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"aws_region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region to deploy resources"&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="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"us-east-1"&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;"cluster_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the EKS cluster"&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="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eks-cluster"&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;"cluster_version"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Kubernetes version for the EKS cluster"&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="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.31"&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;"vpc_cidr"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"CIDR block for VPC"&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="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10.0.0.0/16"&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;"environment"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Environment name (e.g., dev, staging, prod)"&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="nx"&gt;default&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev"&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;Key variables explained:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;aws_region&lt;/code&gt;&lt;/strong&gt; — Where your cluster will be deployed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cluster_name&lt;/code&gt;&lt;/strong&gt; — Identifier for your EKS cluster&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;cluster_version&lt;/code&gt;&lt;/strong&gt; — Kubernetes version (use a recent stable version)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;vpc_cidr&lt;/code&gt;&lt;/strong&gt; — IP address range for your VPC (10.0.0.0/16 gives you 65,536 IPs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;environment&lt;/code&gt;&lt;/strong&gt; — Useful for tagging and distinguishing environments&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Step 4: Main Configuration (VPC &amp;amp; EKS)
&lt;/h2&gt;

&lt;p&gt;This is where the magic happens. We'll use community modules to create our VPC and EKS cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;main.tf&lt;/code&gt;&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;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# Data Sources&lt;/span&gt;
&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_availability_zones"&lt;/span&gt; &lt;span class="s2"&gt;"available"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;filter&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="s2"&gt;"opt-in-status"&lt;/span&gt;
    &lt;span class="nx"&gt;values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"opt-in-not-required"&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="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# Local Variables&lt;/span&gt;
&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;locals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;azs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&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;environment&lt;/span&gt;
    &lt;span class="nx"&gt;Terraform&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"true"&lt;/span&gt;
    &lt;span class="nx"&gt;Project&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eks-at-scale"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# VPC Module&lt;/span&gt;
&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"vpc"&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;"terraform-aws-modules/vpc/aws"&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; 5.0"&lt;/span&gt;

  &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"${var.cluster_name}-vpc"&lt;/span&gt;
  &lt;span class="nx"&gt;cidr&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;vpc_cidr&lt;/span&gt;

  &lt;span class="nx"&gt;azs&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;azs&lt;/span&gt;
  &lt;span class="nx"&gt;private_subnets&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;in&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;azs&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&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;vpc_cidr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="nx"&gt;public_subnets&lt;/span&gt;  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;for&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="nx"&gt;in&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;azs&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;cidrsubnet&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;vpc_cidr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&lt;/span&gt; &lt;span class="err"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

  &lt;span class="nx"&gt;enable_nat_gateway&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;single_nat_gateway&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="nx"&gt;enable_dns_hostnames&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Tags required for EKS&lt;/span&gt;
  &lt;span class="nx"&gt;public_subnet_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/role/elb"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;private_subnet_tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="s2"&gt;"kubernetes.io/role/internal-elb"&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&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;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="c1"&gt;# EKS Module&lt;/span&gt;
&lt;span class="c1"&gt;# -----------------------------------------------------------------------------&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"eks"&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;"terraform-aws-modules/eks/aws"&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; 20.0"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_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;cluster_name&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_version&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;cluster_version&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_endpoint_public_access&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="c1"&gt;# Cluster Add-ons&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_addons&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;coredns&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nx"&gt;eks-pod-identity-agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nx"&gt;kube-proxy&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="nx"&gt;vpc-cni&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;vpc_id&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
  &lt;span class="nx"&gt;subnet_ids&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_subnets&lt;/span&gt;

  &lt;span class="c1"&gt;# EKS Managed Node Group(s)&lt;/span&gt;
  &lt;span class="nx"&gt;eks_managed_node_groups&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ami_type&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AL2023_x86_64_STANDARD"&lt;/span&gt;
      &lt;span class="nx"&gt;instance_types&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"t3.medium"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

      &lt;span class="nx"&gt;min_size&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
      &lt;span class="nx"&gt;max_size&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
      &lt;span class="nx"&gt;desired_size&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;# Cluster access entry&lt;/span&gt;
  &lt;span class="nx"&gt;enable_cluster_creator_admin_permissions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

  &lt;span class="nx"&gt;tags&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;tags&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Breaking Down the Configuration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Data Sources
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_availability_zones"&lt;/span&gt; &lt;span class="s2"&gt;"available"&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fetches available AZs in the region, filtering out any that require opt-in.&lt;/p&gt;

&lt;h4&gt;
  
  
  Local Variables
&lt;/h4&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;azs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_availability_zones&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Selects the first 3 availability zones&lt;/li&gt;
&lt;li&gt;Defines common tags for all resources&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  VPC Module
&lt;/h4&gt;

&lt;p&gt;The VPC module creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC&lt;/strong&gt; with the specified CIDR block&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public subnets&lt;/strong&gt; — For load balancers and NAT gateways&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private subnets&lt;/strong&gt; — For worker nodes (more secure)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateway&lt;/strong&gt; — Allows private subnets to access the internet&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Required tags&lt;/strong&gt; — EKS uses these tags to discover subnets for load balancers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Subnet tagging is critical:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kubernetes.io/role/elb = 1&lt;/code&gt; — Public subnets for internet-facing load balancers&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kubernetes.io/role/internal-elb = 1&lt;/code&gt; — Private subnets for internal load balancers&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  EKS Module
&lt;/h4&gt;

&lt;p&gt;The EKS module creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EKS Cluster&lt;/strong&gt; — The managed control plane&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster Add-ons&lt;/strong&gt; — Essential components (CoreDNS, VPC CNI, kube-proxy)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed Node Group&lt;/strong&gt; — Worker nodes with auto-scaling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Node group configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ami_type: AL2023_x86_64_STANDARD&lt;/code&gt; — Amazon Linux 2023 (latest EKS-optimized AMI)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;instance_types: ["t3.medium"]&lt;/code&gt; — 2 vCPUs, 4GB RAM (good for learning)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;min_size: 2, max_size: 4&lt;/code&gt; — Auto-scaling boundaries&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;desired_size: 2&lt;/code&gt; — Initial number of nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on Instance Types:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
For learning and testing purposes, it is possible to use small instance types such as &lt;code&gt;t2.micro&lt;/code&gt;. However, due to limited CPU and memory, this setup is not recommended for running real workloads. For a more stable experience, instance types like &lt;code&gt;t3.small&lt;/code&gt; or &lt;code&gt;t3.medium&lt;/code&gt; are preferred.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 5: Outputs
&lt;/h2&gt;

&lt;p&gt;Define useful values to display after deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;outputs.tf&lt;/code&gt;&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="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the EKS cluster"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_name&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_endpoint"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Endpoint for EKS control plane"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_endpoint&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_version"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Kubernetes version of the cluster"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_version&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"cluster_security_group_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Security group ID attached to the EKS cluster"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_security_group_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"region"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS region"&lt;/span&gt;
  &lt;span class="nx"&gt;value&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;aws_region&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"vpc_id"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"VPC ID"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vpc_id&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"configure_kubectl"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Command to configure kubectl"&lt;/span&gt;
  &lt;span class="nx"&gt;value&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws eks update-kubeconfig --region ${var.aws_region} --name ${module.eks.cluster_name}"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;configure_kubectl&lt;/code&gt; output is especially helpful — it gives you the exact command to run after deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Deploy the Infrastructure
&lt;/h2&gt;

&lt;p&gt;Now let's provision our EKS cluster!&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialize Terraform
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;eks-terraform
terraform init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This downloads the required providers and modules. You'll see output like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Initializing provider plugins...
- Finding hashicorp/aws versions matching "~&amp;gt; 5.0"...
- Installing hashicorp/aws v5.x.x...

Terraform has been successfully initialized!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Review the Plan
&lt;/h3&gt;



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

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

&lt;/div&gt;



&lt;p&gt;This shows what Terraform will create without making any changes. Review the output carefully — you should see resources for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC and subnets&lt;/li&gt;
&lt;li&gt;Internet Gateway and NAT Gateway&lt;/li&gt;
&lt;li&gt;Route tables&lt;/li&gt;
&lt;li&gt;EKS cluster&lt;/li&gt;
&lt;li&gt;Managed node group&lt;/li&gt;
&lt;li&gt;Security groups&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Apply the Configuration
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; when prompted to confirm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This process takes approximately 10-15 minutes. EKS cluster creation is the longest step.&lt;/p&gt;

&lt;p&gt;You'll see progress output as resources are created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.vpc.aws_vpc.this[0]: Creating...
module.vpc.aws_vpc.this[0]: Creation complete after 2s
...
module.eks.aws_eks_cluster.this[0]: Creating...
module.eks.aws_eks_cluster.this[0]: Still creating... [5m0s elapsed]
module.eks.aws_eks_cluster.this[0]: Creation complete after 9m32s
...
Apply complete! Resources: 65 added, 0 changed, 0 destroyed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also login to your aws account and you will see your newly created cluster &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftr1n7irfh1tguz92ehq1.png" alt="Cluster on aws" width="800" height="382"&gt;
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Step 7: Configure kubectl &amp;amp; Verify
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Update kubeconfig
&lt;/h3&gt;

&lt;p&gt;After successful deployment, configure kubectl to connect to your cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks update-kubeconfig &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="nt"&gt;--name&lt;/span&gt; eks-cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or use the output from Terraform:&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="si"&gt;$(&lt;/span&gt;terraform output &lt;span class="nt"&gt;-raw&lt;/span&gt; configure_kubectl&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Added new context arn:aws:eks:us-east-1:ACCOUNT_ID:cluster/eks-cluster to /home/user/.kube/config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Verify the Cluster
&lt;/h3&gt;

&lt;p&gt;Check that your nodes are ready:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NAME                             STATUS   ROLES    AGE   VERSION
ip-10-0-1-xxx.ec2.internal      Ready    &amp;lt;none&amp;gt;   5m    v1.31.x
ip-10-0-2-xxx.ec2.internal      Ready    &amp;lt;none&amp;gt;   5m    v1.31.x
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check cluster information:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Verify system pods are running:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;You should see CoreDNS, kube-proxy, and VPC CNI pods running.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Step 8: Clean Up (Important!)
&lt;/h2&gt;

&lt;p&gt;To avoid unnecessary AWS charges, destroy the resources when you're done:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Type &lt;code&gt;yes&lt;/code&gt; to confirm. This will remove all resources created by Terraform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; This is irreversible and will delete your entire cluster and VPC.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Best Practices for IaC
&lt;/h2&gt;

&lt;p&gt;Here are key best practices to follow as you work with Terraform:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use community modules&lt;/strong&gt; — Don't reinvent the wheel; leverage tested modules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pin versions&lt;/strong&gt; — Lock Terraform, provider, and module versions&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep state remote&lt;/strong&gt; — Use S3 + DynamoDB for team collaboration (we'll cover this later)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separate environments&lt;/strong&gt; — Use workspaces or separate directories for dev/staging/prod&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use variables&lt;/strong&gt; — Make your code reusable across environments&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tag everything&lt;/strong&gt; — Tags help with cost tracking and resource management&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review plans carefully&lt;/strong&gt; — Always check &lt;code&gt;terraform plan&lt;/code&gt; before applying&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use .gitignore&lt;/strong&gt; — Never commit &lt;code&gt;.terraform/&lt;/code&gt; or &lt;code&gt;*.tfstate&lt;/code&gt; files&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up a clean Terraform project structure&lt;/li&gt;
&lt;li&gt;Used community modules for VPC and EKS&lt;/li&gt;
&lt;li&gt;Provisioned a production-ready EKS cluster&lt;/li&gt;
&lt;li&gt;Configured kubectl and verified the cluster&lt;/li&gt;
&lt;li&gt;Learned IaC best practices&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You now have a repeatable, version-controlled way to create EKS clusters!&lt;/p&gt;




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

&lt;p&gt;In &lt;strong&gt;Part 4&lt;/strong&gt;, we'll deploy applications to our EKS cluster and explore:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deploying a sample application&lt;/li&gt;
&lt;li&gt;Understanding Kubernetes Deployments and Services&lt;/li&gt;
&lt;li&gt;Exposing applications with Load Balancers&lt;/li&gt;
&lt;li&gt;Introduction to Helm for package management&lt;/li&gt;
&lt;/ul&gt;

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




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest" rel="noopener noreferrer"&gt;Terraform AWS VPC Module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://registry.terraform.io/modules/terraform-aws-modules/eks/aws/latest" rel="noopener noreferrer"&gt;Terraform AWS EKS Module&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://aws.github.io/aws-eks-best-practices/" rel="noopener noreferrer"&gt;Amazon EKS Best Practices Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.terraform.io/docs" rel="noopener noreferrer"&gt;Terraform Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Code Repository
&lt;/h2&gt;

&lt;p&gt;All code from this article is available in the &lt;a href="https://github.com/bansikah22/eks-at-scale/tree/master/eks-terraform" rel="noopener noreferrer"&gt;eks-terraform/&lt;/a&gt; directory of the series repository.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Found this article helpful? Follow along with the series and drop a comment with your questions!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>terraform</category>
      <category>devops</category>
    </item>
    <item>
      <title>Amazon EKS Series - Part 2: EKS Architecture and Core Components</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Wed, 17 Dec 2025 13:40:55 +0000</pubDate>
      <link>https://dev.to/bansikah/amazon-eks-series-part-2-eks-architecture-and-core-components-36gk</link>
      <guid>https://dev.to/bansikah/amazon-eks-series-part-2-eks-architecture-and-core-components-36gk</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome back to the &lt;strong&gt;Amazon EKS at Scale&lt;/strong&gt; series!&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/bansikah/amazon-eks-series-part-1-introduction-to-eks-4m3p"&gt;Part 1&lt;/a&gt;, we covered the fundamentals of Amazon EKS — what it is, why to use it, and the different ways to manage worker nodes. We also created our first EKS cluster using &lt;code&gt;eksctl&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In this article, we'll take a deeper look at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The high-level architecture of Amazon EKS&lt;/li&gt;
&lt;li&gt;Control plane components (AWS-managed)&lt;/li&gt;
&lt;li&gt;Worker nodes (customer-managed)&lt;/li&gt;
&lt;li&gt;Networking fundamentals&lt;/li&gt;
&lt;li&gt;IAM and authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Understanding this architecture is essential before diving into production deployments.&lt;/p&gt;




&lt;h2&gt;
  
  
  High-Level View of Amazon EKS
&lt;/h2&gt;

&lt;p&gt;Amazon EKS is a &lt;strong&gt;managed Kubernetes service&lt;/strong&gt; that runs the Kubernetes control plane for you. This means AWS handles the complex, undifferentiated heavy lifting of running Kubernetes, while you focus on deploying and managing your applications.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  What AWS Manages vs What You Manage
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Managed By&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Control Plane (API Server, etcd, Scheduler, Controllers)&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control Plane High Availability&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Control Plane Security Patches&lt;/td&gt;
&lt;td&gt;AWS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worker Nodes&lt;/td&gt;
&lt;td&gt;You (or AWS with Managed Node Groups/Fargate)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Deployments&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Pod Networking&lt;/td&gt;
&lt;td&gt;You (with AWS VPC CNI)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IAM Roles and Policies&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This shared responsibility model allows you to leverage AWS's expertise in running highly available infrastructure while maintaining control over your workloads.&lt;/p&gt;




&lt;h2&gt;
  
  
  EKS Control Plane (AWS-Managed)
&lt;/h2&gt;

&lt;p&gt;The control plane is the brain of your Kubernetes cluster. In EKS, AWS fully manages this component, running it across &lt;strong&gt;multiple Availability Zones&lt;/strong&gt; for high availability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Core Control Plane Components
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Kubernetes API Server (&lt;code&gt;kube-apiserver&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;The API server is the &lt;strong&gt;front door&lt;/strong&gt; to your Kubernetes cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Exposes the Kubernetes API over HTTPS&lt;/li&gt;
&lt;li&gt;Validates and processes all API requests (from &lt;code&gt;kubectl&lt;/code&gt;, controllers, and other components)&lt;/li&gt;
&lt;li&gt;Acts as the gateway for all cluster operations — creating pods, services, deployments, etc.&lt;/li&gt;
&lt;li&gt;Authenticates requests using AWS IAM (via the AWS IAM Authenticator)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When you run &lt;code&gt;kubectl get pods&lt;/code&gt;, your request goes to the API server, which retrieves the information from etcd and returns it to you.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. etcd
&lt;/h4&gt;

&lt;p&gt;etcd is a &lt;strong&gt;distributed key-value store&lt;/strong&gt; that serves as Kubernetes' database:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stores all cluster state and configuration data&lt;/li&gt;
&lt;li&gt;Holds information about pods, services, secrets, ConfigMaps, and more&lt;/li&gt;
&lt;li&gt;Provides strong consistency guarantees&lt;/li&gt;
&lt;li&gt;In EKS, AWS manages etcd replication across multiple Availability Zones&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You never interact with etcd directly — all access goes through the API server.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Scheduler (&lt;code&gt;kube-scheduler&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;The scheduler is responsible for &lt;strong&gt;placing pods on nodes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Watches for newly created pods that have no node assigned&lt;/li&gt;
&lt;li&gt;Evaluates resource requirements (CPU, memory, storage)&lt;/li&gt;
&lt;li&gt;Considers constraints like node selectors, taints, tolerations, and affinity rules&lt;/li&gt;
&lt;li&gt;Selects the most suitable node and binds the pod to it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scheduler ensures efficient resource utilization across your cluster.&lt;/p&gt;

&lt;h4&gt;
  
  
  4. Controller Manager (&lt;code&gt;kube-controller-manager&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;The controller manager runs &lt;strong&gt;control loops&lt;/strong&gt; that regulate cluster state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node Controller&lt;/strong&gt; — Monitors node health and responds when nodes go down&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replication Controller&lt;/strong&gt; — Ensures the correct number of pod replicas are running&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Endpoints Controller&lt;/strong&gt; — Populates endpoint objects (joins Services and Pods)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Account Controller&lt;/strong&gt; — Creates default service accounts for new namespaces&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each controller watches the current state and works to move it toward the desired state.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Characteristics of EKS Control Plane
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Runs in an AWS-managed VPC&lt;/strong&gt; — Isolated from your account and other customers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highly available&lt;/strong&gt; — At least two API server instances and three etcd nodes across multiple AZs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatically scaled&lt;/strong&gt; — AWS scales control plane resources based on cluster size&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No direct access&lt;/strong&gt; — You cannot SSH into control plane nodes; you interact only via the API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic updates&lt;/strong&gt; — AWS handles patching and security updates&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Worker Nodes (Customer-Managed)
&lt;/h2&gt;

&lt;p&gt;Worker nodes are the &lt;strong&gt;compute resources&lt;/strong&gt; where your applications actually run. Unlike the control plane, you are responsible for provisioning and managing worker nodes (unless using Fargate).&lt;/p&gt;

&lt;h3&gt;
  
  
  Worker Node Components
&lt;/h3&gt;

&lt;p&gt;Each worker node runs several Kubernetes components:&lt;/p&gt;

&lt;h4&gt;
  
  
  1. kubelet
&lt;/h4&gt;

&lt;p&gt;The kubelet is the &lt;strong&gt;primary node agent&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Registers the node with the Kubernetes API server&lt;/li&gt;
&lt;li&gt;Watches for pods scheduled to its node&lt;/li&gt;
&lt;li&gt;Ensures containers are running and healthy&lt;/li&gt;
&lt;li&gt;Reports node and pod status back to the control plane&lt;/li&gt;
&lt;li&gt;Executes liveness and readiness probes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The kubelet communicates with the container runtime to manage container lifecycle.&lt;/p&gt;

&lt;h4&gt;
  
  
  2. kube-proxy
&lt;/h4&gt;

&lt;p&gt;kube-proxy handles &lt;strong&gt;networking on each node&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Maintains network rules for pod-to-pod communication&lt;/li&gt;
&lt;li&gt;Implements Kubernetes Services (ClusterIP, NodePort, LoadBalancer)&lt;/li&gt;
&lt;li&gt;Uses iptables or IPVS to route traffic to the correct pods&lt;/li&gt;
&lt;li&gt;Enables service discovery within the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  3. Container Runtime
&lt;/h4&gt;

&lt;p&gt;The container runtime &lt;strong&gt;executes containers&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EKS uses &lt;code&gt;containerd&lt;/code&gt; as the default runtime (Docker support was deprecated in Kubernetes 1.24)&lt;/li&gt;
&lt;li&gt;Pulls container images from registries (like Amazon ECR)&lt;/li&gt;
&lt;li&gt;Creates and manages container processes&lt;/li&gt;
&lt;li&gt;Handles container isolation using Linux namespaces and cgroups&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Worker Node Options in EKS
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Node Management&lt;/th&gt;
&lt;th&gt;Scaling&lt;/th&gt;
&lt;th&gt;Best For&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Self-Managed Nodes&lt;/td&gt;
&lt;td&gt;You manage everything&lt;/td&gt;
&lt;td&gt;Manual or custom&lt;/td&gt;
&lt;td&gt;Full control, custom AMIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed Node Groups&lt;/td&gt;
&lt;td&gt;AWS manages provisioning and updates&lt;/td&gt;
&lt;td&gt;Auto Scaling Groups&lt;/td&gt;
&lt;td&gt;Most production workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Fargate&lt;/td&gt;
&lt;td&gt;AWS manages everything&lt;/td&gt;
&lt;td&gt;Automatic per-pod&lt;/td&gt;
&lt;td&gt;Serverless, variable workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Networking in EKS
&lt;/h2&gt;

&lt;p&gt;Networking is a critical aspect of any Kubernetes deployment. EKS integrates deeply with AWS networking services.&lt;/p&gt;

&lt;h3&gt;
  
  
  VPC (Virtual Private Cloud)
&lt;/h3&gt;

&lt;p&gt;Your EKS cluster runs inside a &lt;strong&gt;VPC&lt;/strong&gt; — an isolated virtual network in AWS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Provides network isolation and security&lt;/li&gt;
&lt;li&gt;You define the IP address range (CIDR block)&lt;/li&gt;
&lt;li&gt;Contains subnets, route tables, and internet gateways&lt;/li&gt;
&lt;li&gt;EKS requires a VPC with subnets in at least two Availability Zones&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Subnets
&lt;/h3&gt;

&lt;p&gt;Subnets divide your VPC into smaller network segments:&lt;/p&gt;

&lt;h4&gt;
  
  
  Public Subnets
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Have a route to an Internet Gateway&lt;/li&gt;
&lt;li&gt;Resources can have public IP addresses&lt;/li&gt;
&lt;li&gt;Used for load balancers and bastion hosts&lt;/li&gt;
&lt;li&gt;NAT Gateways are placed here for private subnet internet access&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Private Subnets
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;No direct route to the internet&lt;/li&gt;
&lt;li&gt;Access the internet via NAT Gateway (for pulling images, etc.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recommended for worker nodes&lt;/strong&gt; — provides better security&lt;/li&gt;
&lt;li&gt;Pods and nodes are not directly accessible from the internet&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AWS VPC CNI (Container Network Interface)
&lt;/h3&gt;

&lt;p&gt;EKS uses the &lt;strong&gt;Amazon VPC CNI plugin&lt;/strong&gt; for pod networking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each pod gets a &lt;strong&gt;real IP address&lt;/strong&gt; from your VPC CIDR range&lt;/li&gt;
&lt;li&gt;Pods can communicate directly with other AWS services (RDS, ElastiCache, etc.)&lt;/li&gt;
&lt;li&gt;No overlay network — native VPC networking performance&lt;/li&gt;
&lt;li&gt;Supports security groups for pods (with specific configurations)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;How it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The CNI plugin attaches Elastic Network Interfaces (ENIs) to worker nodes&lt;/li&gt;
&lt;li&gt;Each ENI can have multiple secondary IP addresses&lt;/li&gt;
&lt;li&gt;These IPs are assigned to pods running on the node&lt;/li&gt;
&lt;li&gt;Pod-to-pod traffic uses native VPC routing&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Important consideration:&lt;/strong&gt; The number of pods per node is limited by the number of ENIs and IPs the instance type supports.&lt;/p&gt;

&lt;h3&gt;
  
  
  Simplified Network Flow
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────────────────────────────────────────────────────┐
│                         AWS Cloud                           │
│  ┌───────────────────────────────────────────────────────┐  │
│  │                      Your VPC                          │  │
│  │  ┌─────────────────┐    ┌─────────────────┐           │  │
│  │  │  Public Subnet  │    │  Public Subnet  │           │  │
│  │  │   (AZ-1)        │    │   (AZ-2)        │           │  │
│  │  │  Load Balancer  │    │  NAT Gateway    │           │  │
│  │  └─────────────────┘    └─────────────────┘           │  │
│  │  ┌─────────────────┐    ┌─────────────────┐           │  │
│  │  │ Private Subnet  │    │ Private Subnet  │           │  │
│  │  │   (AZ-1)        │    │   (AZ-2)        │           │  │
│  │  │  Worker Nodes   │    │  Worker Nodes   │           │  │
│  │  │  (Pods)         │    │  (Pods)         │           │  │
│  │  └─────────────────┘    └─────────────────┘           │  │
│  └───────────────────────────────────────────────────────┘  │
│                              │                              │
│  ┌───────────────────────────┴───────────────────────────┐  │
│  │           EKS Control Plane (AWS-Managed)             │  │
│  │     API Server  │  etcd  │  Scheduler  │  Controllers │  │
│  └───────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  IAM and Authentication
&lt;/h2&gt;

&lt;p&gt;EKS uses &lt;strong&gt;AWS IAM&lt;/strong&gt; for authentication, providing a secure and familiar way to manage access to your cluster.&lt;/p&gt;

&lt;h3&gt;
  
  
  How Authentication Works
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;User runs kubectl command&lt;/strong&gt; — e.g., &lt;code&gt;kubectl get pods&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS IAM Authenticator&lt;/strong&gt; — kubectl uses the AWS CLI to get a token&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token sent to API Server&lt;/strong&gt; — The token is included in the request&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EKS validates the token&lt;/strong&gt; — Confirms the IAM identity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes RBAC&lt;/strong&gt; — Determines what the user can do&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  IAM vs Kubernetes RBAC
&lt;/h3&gt;

&lt;p&gt;These are two separate but complementary systems:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aspect&lt;/th&gt;
&lt;th&gt;AWS IAM&lt;/th&gt;
&lt;th&gt;Kubernetes RBAC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Purpose&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;Who&lt;/em&gt; can access the cluster&lt;/td&gt;
&lt;td&gt;
&lt;em&gt;What&lt;/em&gt; they can do inside&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS account level&lt;/td&gt;
&lt;td&gt;Kubernetes cluster level&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Managed by&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;AWS IAM policies&lt;/td&gt;
&lt;td&gt;Kubernetes Role/ClusterRole&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"User X can call EKS APIs"&lt;/td&gt;
&lt;td&gt;"User X can list pods in namespace Y"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The aws-auth ConfigMap
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;aws-auth&lt;/code&gt; ConfigMap maps IAM identities to Kubernetes users and groups:&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;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-auth&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kube-system&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;mapRoles&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;- rolearn: arn:aws:iam::123456789012:role/NodeInstanceRole&lt;/span&gt;
      &lt;span class="s"&gt;username: system:node:{{EC2PrivateDNSName}}&lt;/span&gt;
      &lt;span class="s"&gt;groups:&lt;/span&gt;
        &lt;span class="s"&gt;- system:bootstrappers&lt;/span&gt;
        &lt;span class="s"&gt;- system:nodes&lt;/span&gt;
  &lt;span class="na"&gt;mapUsers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;- userarn: arn:aws:iam::123456789012:user/admin&lt;/span&gt;
      &lt;span class="s"&gt;username: admin&lt;/span&gt;
      &lt;span class="s"&gt;groups:&lt;/span&gt;
        &lt;span class="s"&gt;- system:masters&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Worker nodes use IAM roles mapped in &lt;code&gt;aws-auth&lt;/code&gt; to join the cluster&lt;/li&gt;
&lt;li&gt;You can map IAM users and roles to Kubernetes groups&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;system:masters&lt;/code&gt; group has full cluster admin access&lt;/li&gt;
&lt;li&gt;EKS also supports EKS access entries (a newer, simpler alternative to aws-auth)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Best Practices for IAM in EKS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use IAM roles, not users&lt;/strong&gt; — Roles are more secure and support temporary credentials&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Follow least privilege&lt;/strong&gt; — Grant only the permissions needed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use IRSA (IAM Roles for Service Accounts)&lt;/strong&gt; — Allow pods to assume IAM roles for AWS API access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit access regularly&lt;/strong&gt; — Review who has access to your cluster&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this article, we explored the architecture of Amazon EKS:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EKS is a managed Kubernetes service&lt;/strong&gt; — AWS handles the control plane, you manage worker nodes and applications&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control plane components&lt;/strong&gt; — API Server, etcd, Scheduler, and Controller Manager work together to manage cluster state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker nodes&lt;/strong&gt; — Run kubelet, kube-proxy, and container runtime to execute your workloads&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Networking&lt;/strong&gt; — EKS uses VPC networking with the AWS VPC CNI plugin, giving pods real VPC IP addresses&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IAM integration&lt;/strong&gt; — Authentication uses AWS IAM, while authorization uses Kubernetes RBAC&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;The EKS control plane runs in an AWS-managed VPC, highly available across multiple AZs&lt;/li&gt;
&lt;li&gt;You never access control plane nodes directly — only through the Kubernetes API&lt;/li&gt;
&lt;li&gt;Worker nodes run in your VPC and are your responsibility (unless using Fargate)&lt;/li&gt;
&lt;li&gt;Pods get real VPC IP addresses, enabling direct communication with AWS services&lt;/li&gt;
&lt;li&gt;IAM handles &lt;em&gt;authentication&lt;/em&gt; (who you are), RBAC handles &lt;em&gt;authorization&lt;/em&gt; (what you can do)&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;aws-auth&lt;/code&gt; ConfigMap bridges IAM identities to Kubernetes users&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;In &lt;strong&gt;Part 3&lt;/strong&gt;, we'll get hands-on and provision an Amazon EKS cluster using &lt;strong&gt;Terraform&lt;/strong&gt; and community modules. You'll learn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Setting up Terraform for EKS&lt;/li&gt;
&lt;li&gt;Using the terraform-aws-eks module&lt;/li&gt;
&lt;li&gt;Configuring VPC, subnets, and node groups&lt;/li&gt;
&lt;li&gt;Best practices for infrastructure as code&lt;/li&gt;
&lt;/ul&gt;

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




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/what-is-eks.html" rel="noopener noreferrer"&gt;Amazon EKS Architecture - AWS Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/pod-networking.html" rel="noopener noreferrer"&gt;Amazon VPC CNI Plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/cluster-auth.html" rel="noopener noreferrer"&gt;Cluster Authentication - AWS Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/concepts/overview/components/" rel="noopener noreferrer"&gt;Kubernetes Components&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Found this article helpful? Follow along with the series and share your thoughts in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>eks</category>
      <category>devops</category>
    </item>
    <item>
      <title>Amazon EKS Series - Part 1: Introduction to EKS</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Wed, 17 Dec 2025 10:40:15 +0000</pubDate>
      <link>https://dev.to/bansikah/amazon-eks-series-part-1-introduction-to-eks-4m3p</link>
      <guid>https://dev.to/bansikah/amazon-eks-series-part-1-introduction-to-eks-4m3p</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to the first part of the &lt;strong&gt;Amazon EKS at Scale&lt;/strong&gt; series! In this series, we'll explore Amazon Elastic Kubernetes Service (EKS) from the ground up, covering everything from basic concepts to advanced cluster management.&lt;/p&gt;

&lt;p&gt;In this article, we'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is Amazon EKS?&lt;/li&gt;
&lt;li&gt;Why use EKS?&lt;/li&gt;
&lt;li&gt;Understanding Worker Node options&lt;/li&gt;
&lt;li&gt;Creating and connecting to an EKS cluster&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What is Amazon EKS?
&lt;/h2&gt;

&lt;p&gt;Amazon Elastic Kubernetes Service (EKS) is a &lt;strong&gt;managed Kubernetes service&lt;/strong&gt; provided by AWS. It simplifies running Kubernetes by handling the complex parts of cluster management for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  What AWS Manages for You
&lt;/h3&gt;

&lt;p&gt;With EKS, AWS takes care of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Provisioning and maintaining master nodes&lt;/strong&gt; — AWS automatically sets up and maintains the master nodes that run the Kubernetes control plane, eliminating the need for you to manage this critical infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Installing control plane processes:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;API Server&lt;/strong&gt; — The front-end for the Kubernetes control plane. It handles all REST requests for modifications to pods, services, and other resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scheduler&lt;/strong&gt; — Watches for newly created pods with no assigned node and selects a node for them to run on based on resource requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;etcd&lt;/strong&gt; — A consistent and highly-available key-value store used as Kubernetes' backing store for all cluster data.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Scaling and backups of the cluster&lt;/strong&gt; — AWS automatically handles scaling the control plane and maintains backups of your cluster state, ensuring high availability.&lt;/p&gt;&lt;/li&gt;

&lt;li&gt;&lt;p&gt;&lt;strong&gt;Patching and updating control plane components&lt;/strong&gt; — Security patches and version updates are managed by AWS, keeping your control plane secure and up-to-date.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;This means you can focus on deploying your applications rather than managing Kubernetes infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Use EKS?
&lt;/h2&gt;

&lt;p&gt;There are several compelling reasons to choose EKS:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Simplified Operations&lt;/strong&gt; — Running and scaling Kubernetes on your own requires significant expertise. You need to manage etcd clusters, configure API servers, and handle upgrades carefully. EKS removes this complexity by providing a production-ready control plane out of the box.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enhanced Security&lt;/strong&gt; — Kubernetes security involves network policies, RBAC, secrets management, and more. EKS integrates natively with AWS IAM for authentication, making it easier to implement least-privilege access. Your control plane runs in an AWS-managed VPC, isolated from other customers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;AWS Integration&lt;/strong&gt; — EKS works seamlessly with services like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM&lt;/strong&gt; for authentication and authorization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC&lt;/strong&gt; for network isolation&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch&lt;/strong&gt; for logging and monitoring&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ELB&lt;/strong&gt; for load balancing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ECR&lt;/strong&gt; for container image storage&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This tight integration simplifies building production-grade applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  Worker Nodes: Your Options
&lt;/h2&gt;

&lt;p&gt;While EKS manages the control plane, &lt;strong&gt;you are responsible for the worker nodes&lt;/strong&gt; (where your applications actually run). There are three ways to set up worker nodes:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Self-Managed Nodes
&lt;/h3&gt;

&lt;p&gt;With self-managed nodes, you have complete control but also full responsibility:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Manual EC2 provisioning&lt;/strong&gt; — You select and launch EC2 instances yourself, choosing instance types, AMIs, and configurations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Install Kubernetes components&lt;/strong&gt; — All worker processes must be installed manually:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;kubelet&lt;/code&gt; — The primary node agent that ensures containers are running in pods&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kube-proxy&lt;/code&gt; — Maintains network rules for pod communication&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container runtime&lt;/strong&gt; — Software that runs containers (e.g., containerd, Docker)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Ongoing maintenance&lt;/strong&gt; — Security patches, OS updates, and Kubernetes version upgrades are your responsibility.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Node registration&lt;/strong&gt; — You must configure nodes to register with the EKS control plane.&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Teams needing full control over node configuration, custom AMIs, or specific compliance requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Managed Node Groups
&lt;/h3&gt;

&lt;p&gt;Managed node groups let AWS handle the heavy lifting while you retain some control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Automated provisioning&lt;/strong&gt; — AWS creates and configures EC2 instances for you using EKS-optimized AMIs that come pre-configured with the necessary Kubernetes components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simplified lifecycle management&lt;/strong&gt; — Create, update, or terminate nodes with a single API call. AWS handles rolling updates gracefully.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto Scaling integration&lt;/strong&gt; — Each node group is backed by an Auto Scaling group, allowing automatic scaling based on demand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed updates&lt;/strong&gt; — AWS can update nodes to new AMI versions while respecting pod disruption budgets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Most production workloads where you want a balance of control and convenience.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. AWS Fargate
&lt;/h3&gt;

&lt;p&gt;Fargate provides a fully serverless approach to running containers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No node management&lt;/strong&gt; — You don't provision, configure, or scale EC2 instances. AWS handles all infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-demand compute&lt;/strong&gt; — Fargate spins up compute resources only when pods are scheduled, and removes them when pods terminate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right-sized resources&lt;/strong&gt; — Based on your pod's CPU and memory requirements, Fargate automatically provisions appropriately sized compute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay-per-use pricing&lt;/strong&gt; — You only pay for the vCPU and memory your pods actually consume, billed per second.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Variable or unpredictable workloads, cost optimization, and teams wanting zero infrastructure management.&lt;/p&gt;




&lt;h2&gt;
  
  
  Creating an EKS Cluster
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;To create an EKS cluster, you need to configure several components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cluster configuration:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cluster name&lt;/strong&gt; — A unique identifier for your cluster within the region&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Kubernetes version&lt;/strong&gt; — The version of Kubernetes to run (EKS supports multiple versions)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;IAM role for the cluster&lt;/strong&gt; — An IAM role that grants EKS permissions to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provision nodes&lt;/strong&gt; — Create and manage EC2 instances for worker nodes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Access storage&lt;/strong&gt; — Interact with EBS volumes and other storage services&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Manage secrets&lt;/strong&gt; — Access AWS Secrets Manager or Systems Manager Parameter Store&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Networking:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC and Subnets&lt;/strong&gt; — The network where your cluster will run. Subnets should span multiple Availability Zones for high availability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security groups&lt;/strong&gt; — Firewall rules controlling traffic to and from your cluster components&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Creating Worker Nodes
&lt;/h3&gt;

&lt;p&gt;After creating the cluster, set up your node group:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a Node Group (a group of nodes for your Kubernetes environment)&lt;/li&gt;
&lt;li&gt;Select instance type&lt;/li&gt;
&lt;li&gt;Define min/max number of nodes&lt;/li&gt;
&lt;li&gt;Specify which EKS cluster to connect to&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Methods to Create an EKS Cluster
&lt;/h2&gt;

&lt;p&gt;There are three primary ways to create an EKS cluster:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. AWS Console
&lt;/h3&gt;

&lt;p&gt;The web-based approach - more steps involved but provides a visual interface.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;a href="https://docs.aws.amazon.com/eks/latest/eksctl/what-is-eksctl.html" rel="noopener noreferrer"&gt;eksctl CLI&lt;/a&gt;
&lt;/h3&gt;

&lt;p&gt;A command-line tool that simplifies cluster creation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl create cluster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Security groups&lt;/li&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Subnets&lt;/li&gt;
&lt;li&gt;Node groups&lt;/li&gt;
&lt;li&gt;And more...&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Infrastructure as Code (IaC)
&lt;/h3&gt;

&lt;p&gt;Using tools like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Pulumi&lt;/li&gt;
&lt;li&gt;AWS CloudFormation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Reproducible, version-controlled infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hands-On Demo: Creating Your First EKS Cluster
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Install eksctl
&lt;/h3&gt;

&lt;p&gt;Follow the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html" rel="noopener noreferrer"&gt;official AWS documentation&lt;/a&gt; to install eksctl OR you can use these steps on this github profile to install both &lt;code&gt;kubectl&lt;/code&gt; and &lt;code&gt;eksctl&lt;/code&gt;, these steps works for &lt;code&gt;Linux&lt;/code&gt;, &lt;code&gt;MacOS&lt;/code&gt; and &lt;code&gt;Windows&lt;/code&gt; &lt;a href="https://github.com/bansikah22/eks-at-scale/blob/master/setup/manual/install-tools.md" rel="noopener noreferrer"&gt;Link to github repo&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Ensure AWS Authentication
&lt;/h3&gt;

&lt;p&gt;Make sure you can &lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html" rel="noopener noreferrer"&gt;awsctl&lt;/a&gt; installed so that you can correctly setup your credentials&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Explore eksctl Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Get help&lt;/span&gt;
eksctl &lt;span class="nt"&gt;--help&lt;/span&gt;

&lt;span class="c"&gt;# Explore create options&lt;/span&gt;
eksctl create &lt;span class="nt"&gt;--help&lt;/span&gt;

&lt;span class="c"&gt;# Explore cluster creation options&lt;/span&gt;
eksctl create cluster &lt;span class="nt"&gt;--help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Create the Cluster
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl create cluster &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; cluster1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--nodegroup-name&lt;/span&gt; ng1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--node-type&lt;/span&gt; t2.micro &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--nodes&lt;/span&gt; 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Breakdown of Each Option
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;eksctl create cluster&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Creates a new Amazon EKS cluster and provisions the required AWS infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;-n cluster1&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Sets the name of the EKS cluster to &lt;code&gt;cluster1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;--nodegroup-name ng1&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Defines the name of the managed node group as &lt;code&gt;ng1&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;--region us-east-1&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Specifies the AWS region where the EKS cluster will be created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;--node-type t2.micro&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Chooses the EC2 instance type for worker nodes in the node group.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;--nodes 2&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Sets the desired number of worker nodes in the node group to 2.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  What This Command Creates
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;An Amazon EKS control plane managed by AWS&lt;/li&gt;
&lt;li&gt;A managed EC2 node group named &lt;code&gt;ng1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Two EC2 worker nodes using the &lt;code&gt;t2.micro&lt;/code&gt; instance type&lt;/li&gt;
&lt;li&gt;Required networking resources (VPC, subnets, and security groups) if they don’t already exist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wait for the provisioning to complete.&lt;/p&gt;

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

&lt;p&gt;It takes quite some time because it needs to create like the whole infrastructure for your, the Subnets, Node groups and so ... when it is done you will have something like this &lt;/p&gt;

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

&lt;h3&gt;
  
  
  Step 5: Verify Created Resources
&lt;/h3&gt;

&lt;p&gt;After creation, check the following in AWS Console:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Subnets&lt;/li&gt;
&lt;li&gt;EKS Services&lt;/li&gt;
&lt;li&gt;Compute &amp;gt; Node Group&lt;/li&gt;
&lt;li&gt;EC2 Instances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First you can search EKS and you will see that our cluster has ben created &lt;/p&gt;

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

&lt;p&gt;and if you look further you will see the version of kubernetes we have &lt;/p&gt;

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

&lt;p&gt;We also have our Node group created with our nodes(EC2) you can see&lt;/p&gt;

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

&lt;p&gt;And if you check the instances you will see that our instances are up and running ...&lt;/p&gt;

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

&lt;p&gt;And you can also see the security group and so &lt;/p&gt;

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




&lt;h2&gt;
  
  
  Connecting to Your EKS Cluster
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Verify kubectl Configuration
&lt;/h3&gt;

&lt;p&gt;You can verify that your &lt;code&gt;kubeconfig&lt;/code&gt; file has been updated.&lt;br&gt;&lt;br&gt;
The &lt;code&gt;kubeconfig&lt;/code&gt; file tells &lt;code&gt;kubectl&lt;/code&gt; which cluster to connect to and&lt;br&gt;
how to authenticate when communicating with the Kubernetes API server.&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check Your Nodes
&lt;/h3&gt;

&lt;p&gt;You can also get your worker nodes directly from the terminal, and you can clearly see that using the tool &lt;code&gt;eksctl&lt;/code&gt; all the cluster creation and worker nodes connection is automatically done for you&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;p&gt;If you see your worker nodes listed, your connection is successful!&lt;/p&gt;




&lt;h2&gt;
  
  
  Cleaning Up
&lt;/h2&gt;

&lt;p&gt;To avoid unnecessary charges, delete your cluster when done:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eksctl delete cluster &lt;span class="nt"&gt;-n&lt;/span&gt; cluster1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;In this first part of the series, we covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;What EKS is&lt;/strong&gt; - A managed Kubernetes service where AWS handles the control plane&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why use EKS&lt;/strong&gt; - Simplified operations, enhanced security, and AWS integration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker node options&lt;/strong&gt; - Self-managed, Managed Node Groups, and Fargate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cluster creation methods&lt;/strong&gt; - Console, eksctl, and IaC&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Basic cluster operations&lt;/strong&gt; - Creating, connecting, and deleting clusters&lt;/li&gt;
&lt;/ul&gt;




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

&lt;p&gt;In &lt;strong&gt;Part 2&lt;/strong&gt;, we'll dive deeper into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EKS architecture and components&lt;/li&gt;
&lt;li&gt;How to Setup an EKS cluster using terraform.&lt;/li&gt;
&lt;/ul&gt;

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




&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/" rel="noopener noreferrer"&gt;Amazon EKS Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://eksctl.io/" rel="noopener noreferrer"&gt;eksctl Official Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/docs/" rel="noopener noreferrer"&gt;Kubernetes Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Did you find this article helpful? Follow along with the series and drop a comment with your questions!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>eks</category>
      <category>devops</category>
    </item>
    <item>
      <title>Achieving Least Privilege in Docker</title>
      <dc:creator>Tandap Noel Bansikah</dc:creator>
      <pubDate>Sat, 08 Nov 2025 11:29:35 +0000</pubDate>
      <link>https://dev.to/bansikah/achieving-least-privilege-in-docker-33je</link>
      <guid>https://dev.to/bansikah/achieving-least-privilege-in-docker-33je</guid>
      <description>&lt;p&gt;Ensuring containers run with only the permissions they truly need is one of the most effective defenses against compromise. The Docker documentation emphasises &lt;em&gt;least privilege&lt;/em&gt; as a core security practice—limit what a container can do, lower the blast radius if it is exploited, and make abuse easier to detect.&lt;sup id="fnref1"&gt;1&lt;/sup&gt; This guide walks through key controls Docker exposes, and finishes with a repeatable hands-on exercise you can run locally.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. What “Least Privilege” Means for Containers
&lt;/h2&gt;

&lt;p&gt;At its heart, least privilege is the idea that workloads only need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the minimum filesystem access required to operate&lt;/li&gt;
&lt;li&gt;a non-root identity, so the container cannot trivially take over the host&lt;/li&gt;
&lt;li&gt;only the Linux capabilities that map to the tasks it performs&lt;/li&gt;
&lt;li&gt;explicit resource limits to contain runaway processes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Docker exposes controls for every one of these dimensions through both the &lt;code&gt;Dockerfile&lt;/code&gt; (build-time choices) and &lt;code&gt;docker run&lt;/code&gt; flags (runtime policy).&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Build-Time Controls (Dockerfile)
&lt;/h2&gt;

&lt;p&gt;The official guidelines highlight three quick wins:&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use a minimal base image&lt;/strong&gt; (e.g., &lt;code&gt;distroless&lt;/code&gt; or Alpine) so the container inherits fewer default binaries and attack surface.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create and switch to a non-root user&lt;/strong&gt; with only the filesystem paths it needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope file ownership and permissions&lt;/strong&gt; so write access is limited.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.12-slim&lt;/span&gt;

&lt;span class="c"&gt;# Create a system user and group&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;addgroup &lt;span class="nt"&gt;--system&lt;/span&gt; app &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; adduser &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="nt"&gt;--ingroup&lt;/span&gt; app app

&lt;span class="c"&gt;# Copy application code and drop ownership to the app user&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /opt/service&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=app:app requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;--no-cache-dir&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --chown=app:app . .&lt;/span&gt;

&lt;span class="c"&gt;# Run as the non-root user&lt;/span&gt;
&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; app&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["python", "app.py"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Key points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;USER&lt;/code&gt; directive ensures all subsequent layers (and the running process) inherit the non-root identity.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--chown&lt;/code&gt; on &lt;code&gt;COPY&lt;/code&gt; prevents root-owned artefacts, which could otherwise allow privilege escalation if the runtime tried to write to those files.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  3. Runtime Controls (&lt;code&gt;docker run&lt;/code&gt;)
&lt;/h2&gt;

&lt;p&gt;Even with a hardened image, Docker defaults to granting capabilities beyond what most applications need. Official docs recommend &lt;em&gt;dropping all capabilities by default&lt;/em&gt;, then re-adding only the ones the workload relies on.&lt;sup id="fnref2"&gt;2&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;Here are the runtime flags we will use in the practical section:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Why it matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--user&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Overrides the container’s user ID at runtime. Using the same non-root ID as the Dockerfile keeps both layers consistent.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--cap-drop ALL&lt;/code&gt; and &lt;code&gt;--cap-add&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Remove every capability, then add back a handful—for example &lt;code&gt;NET_BIND_SERVICE&lt;/code&gt; for binding to privileged ports.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--read-only&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Mount the container filesystem as read-only; pair with writable volumes for state that must persist.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--tmpfs&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Provide ephemeral writable space (e.g., &lt;code&gt;/tmp&lt;/code&gt;) without broad write access elsewhere.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--security-opt no-new-privileges&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Blocks setuid binaries and other mechanisms from elevating privilege inside the container.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;--memory&lt;/code&gt;, &lt;code&gt;--cpus&lt;/code&gt;, &lt;code&gt;--pids-limit&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Resource governance—if the process is compromised it cannot starve the host.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  4. Hands-On: Locking Down a Simple Web Service
&lt;/h2&gt;

&lt;p&gt;The following example runs a tiny Flask service with progressively tighter permissions. You can adapt it to your own applications by swapping out the base image and command.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.1 Prepare the demo
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; app.py
from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Least privilege demo!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; requirements.txt
flask==3.0.2
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;' &amp;gt; Dockerfile
FROM python:3.12-slim

RUN addgroup --system app &amp;amp;&amp;amp; adduser --system --ingroup app app
WORKDIR /opt/service
COPY --chown=app:app requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY --chown=app:app app.py .

USER app
CMD ["python", "app.py"]
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; least-privilege-demo:latest &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 Run with default privileges (baseline)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 5000:5000 least-privilege-demo:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works, but the container still has many capabilities and a writable root filesystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Re-run with least privilege
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; least-priv &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--user&lt;/span&gt; app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--read-only&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tmpfs&lt;/span&gt; /tmp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-drop&lt;/span&gt; ALL &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cap-add&lt;/span&gt; NET_BIND_SERVICE &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--security-opt&lt;/span&gt; no-new-privileges &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--memory&lt;/span&gt; 256m &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpus&lt;/span&gt; 0.5 &lt;span class="se"&gt;\&lt;/span&gt;
  least-privilege-demo:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;What changed:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The filesystem is now read-only, except for the &lt;code&gt;tmpfs&lt;/code&gt; mount at &lt;code&gt;/tmp&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Capabilities are stripped down to one—the app can bind to port 5000 (above 1024), so technically it would work even without &lt;code&gt;NET_BIND_SERVICE&lt;/code&gt;, but this illustrates how to add back the minimum set.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;no-new-privileges&lt;/code&gt; ensures setuid binaries cannot escalate.&lt;/li&gt;
&lt;li&gt;Resource limits prevent denial-of-service side effects.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify the container is healthy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl http://localhost:5000
docker &lt;span class="nb"&gt;exec &lt;/span&gt;least-priv &lt;span class="nb"&gt;id
&lt;/span&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;least-priv &lt;span class="nb"&gt;cat&lt;/span&gt; /proc/self/status | &lt;span class="nb"&gt;grep &lt;/span&gt;CapEff
docker &lt;span class="nb"&gt;exec &lt;/span&gt;least-priv &lt;span class="nb"&gt;touch&lt;/span&gt; /tmp/test-file &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; docker &lt;span class="nb"&gt;exec &lt;/span&gt;least-priv &lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /tmp/test-file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl http://localhost:5000
Least privilege demo!

$ docker exec least-priv id
uid=100(app) gid=101(app) groups=101(app)

$ docker exec least-priv cat /proc/self/status | grep CapEff
CapEff: 0000000000000000

$ docker exec least-priv ls -l /tmp/test-file
-rw-r--r-- 1 app app 0 Nov  8 08:04 /tmp/test-file
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The zeroed-out &lt;code&gt;CapEff&lt;/code&gt; confirms all capabilities are dropped. Trying to write anywhere other than &lt;code&gt;/tmp&lt;/code&gt; should fail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;exec &lt;/span&gt;least-priv &lt;span class="nb"&gt;touch&lt;/span&gt; /opt/service/newfile
&lt;span class="c"&gt;# touch: cannot touch '/opt/service/newfile': Read-only file system&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.4 Clean up
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; least-priv
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; Dockerfile app.py requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  5. Additional Hardening Ideas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rootless Docker:&lt;/strong&gt; Run the Docker daemon itself without root privileges.&lt;sup id="fnref3"&gt;3&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated AppArmor/SELinux Profiles:&lt;/strong&gt; Limit system calls and file access even further.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read-only Secrets:&lt;/strong&gt; Mount configuration through Docker secrets or Kubernetes ConfigMaps and avoid baking secrets into images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply chain controls:&lt;/strong&gt; Pin base image digests and verify them before promotion.&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;Least privilege is a principle; Docker gives you the tooling to apply it.&lt;/li&gt;
&lt;li&gt;Small, non-root images eliminate entire classes of privilege escalation.&lt;/li&gt;
&lt;li&gt;Runtime flags let you defend in depth—capabilities, filesystem access, resource limits.&lt;/li&gt;
&lt;li&gt;The pattern mirrors Kubernetes security contexts, so what you practice locally carries over to orchestrated environments.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With these habits, containers remain focused on the work they should do—and little else.&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;Docker Docs — &lt;a href="https://docs.docker.com/build/building/best-practices/#security" rel="noopener noreferrer"&gt;Apply security best practices to Dockerfiles&lt;/a&gt; and &lt;a href="https://docs.docker.com/engine/security/rootless/" rel="noopener noreferrer"&gt;Runtime privilege &amp;amp; Linux capabilities&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Docker Docs — &lt;a href="https://docs.docker.com/engine/security/capabilities/" rel="noopener noreferrer"&gt;Linux capabilities in practice&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn3"&gt;
&lt;p&gt;Docker Docs — &lt;a href="https://docs.docker.com/engine/security/rootless/" rel="noopener noreferrer"&gt;Run the Docker daemon as a non-root user (Rootless mode)&lt;/a&gt;.&amp;nbsp;↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>docker</category>
      <category>container</category>
      <category>security</category>
      <category>python</category>
    </item>
  </channel>
</rss>
