<?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: BAKRE JAMIU</title>
    <description>The latest articles on DEV Community by BAKRE JAMIU (@jamiu_cloud).</description>
    <link>https://dev.to/jamiu_cloud</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%2F2032476%2F7b90aad0-a783-4dfa-aa65-4b7f12a24be4.jpg</url>
      <title>DEV Community: BAKRE JAMIU</title>
      <link>https://dev.to/jamiu_cloud</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jamiu_cloud"/>
    <language>en</language>
    <item>
      <title>Building a CI/CD and GitOps Platform with Jenkins, Docker, Kubernetes and ArgoCD</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Mon, 19 Oct 2026 08:39:35 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/building-a-cicd-and-gitops-platform-with-jenkins-docker-kubernetes-and-argo-cd-my-end-to-end-1nkg</link>
      <guid>https://dev.to/jamiu_cloud/building-a-cicd-and-gitops-platform-with-jenkins-docker-kubernetes-and-argo-cd-my-end-to-end-1nkg</guid>
      <description>&lt;p&gt;&lt;strong&gt;From Jenkins credential errors to a working pipeline, Kubernetes deployment, and automated GitOps reconciliation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; Jamiu Olatunji Bakre&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Role:&lt;/strong&gt; Cloud, DevOps and Platform Engineer&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Tags:&lt;/strong&gt; DevOps, Platform Engineering, Jenkins, CI/CD, Docker, Kubernetes, GitOps, ArgoCD, Maven, Kustomize, Minikube&lt;/p&gt;


&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This project was an important step in my journey towards becoming a stronger Cloud, DevOps and Platform Engineer.&lt;/p&gt;

&lt;p&gt;My goal was to understand how application code moves from a GitHub repository through an automated build process, becomes a Docker image, and is ultimately deployed and managed in Kubernetes.&lt;/p&gt;

&lt;p&gt;I wanted to go beyond simply installing tools and following commands from a tutorial. I wanted to understand how the components communicate, where configuration belongs, how credentials are handled, how deployment problems are investigated, and how to verify that an application is actually working.&lt;/p&gt;

&lt;p&gt;The journey included configuring Jenkins, working with GitHub repositories, building a Spring Boot application with Maven, packaging the application with Docker, and using Kubernetes and ArgoCD to manage the deployment.&lt;/p&gt;

&lt;p&gt;One of the most memorable challenges was a Jenkins credential error that took considerable troubleshooting. The pipeline reported that it could not find a credential with the ID &lt;code&gt;github-credentials&lt;/code&gt;. After working through the integration issues, I achieved a successful pipeline build on my 20th CI/CD pipeline build.&lt;/p&gt;

&lt;p&gt;That experience taught me something important: building a pipeline is not just about writing a Jenkinsfile. It also requires understanding credentials, permissions, repository configuration, networking, container registries, deployment manifests, and the dependencies between each stage.&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%2F6e6phhltbs7m6pj5f0pr.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%2F6e6phhltbs7m6pj5f0pr.png" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ArgoCD Output&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F22j532vzqtmu1m9tj89w.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%2F22j532vzqtmu1m9tj89w.png" alt=" " width="799" height="363"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scaled Up ArgoCD Output&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This documentation explains the architecture, the steps involved, the purpose of each component, the commands used to inspect and validate the system, and the improvements I would make when extending the project.&lt;/p&gt;

&lt;p&gt;It also includes reference configurations for frontend and backend applications, persistent storage, monitoring, and production-oriented practices. These examples provide a foundation for future work; they should not be interpreted as proof that every optional component was deployed in the original lab.&lt;/p&gt;
&lt;h2&gt;
  
  
  1. Project Objectives
&lt;/h2&gt;

&lt;p&gt;The project was designed around the following objectives:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automate application build and verification using Jenkins.&lt;/li&gt;
&lt;li&gt;Store application source code in GitHub.&lt;/li&gt;
&lt;li&gt;Package the application into a Docker image.&lt;/li&gt;
&lt;li&gt;Publish container images to Docker Hub.&lt;/li&gt;
&lt;li&gt;Define Kubernetes resources declaratively.&lt;/li&gt;
&lt;li&gt;Separate application source code from deployment configuration.&lt;/li&gt;
&lt;li&gt;Use ArgoCD to monitor the GitOps repository.&lt;/li&gt;
&lt;li&gt;Test automated reconciliation when the live cluster configuration differs from Git.&lt;/li&gt;
&lt;li&gt;Verify the deployed application's availability through Kubernetes and an HTTP request.&lt;/li&gt;
&lt;li&gt;Develop a repeatable troubleshooting and documentation process.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The wider objective was to understand how these capabilities fit together as a small platform engineering workflow.&lt;/p&gt;
&lt;h2&gt;
  
  
  2. Project Architecture
&lt;/h2&gt;

&lt;p&gt;The architecture separates the application build process from the deployment reconciliation process.&lt;/p&gt;

&lt;p&gt;Jenkins runs on an AWS EC2 instance. The Kubernetes application and ArgoCD run in a local Minikube environment.&lt;/p&gt;

&lt;p&gt;This separation is important because a Jenkins server on EC2 cannot automatically reach a Kubernetes cluster running on a developer's local computer. The two environments have different network boundaries.&lt;/p&gt;
&lt;h3&gt;
  
  
  Architecture diagram
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                    Developer
                        |
                        v
               Application Changes
                        |
                        v
                 GitHub Source Repo
                        |
                        v
                  Jenkins on EC2
                        |
             +----------+-----------+
             |          |           |
             v          v           v
           Maven     Code Checks   Docker Build
             |                      |
             v                      v
        Build/Test               Docker Hub
                                    |
                                    v
                          GitOps Repository
                                    |
                                    v
                                ArgoCD
                                    |
                                    v
                           Local Minikube
                                    |
                                    v
                            Kubernetes
                                    |
                                    v
                         Spring Boot App
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The diagram describes the intended delivery flow. The exact automation depends on which stages are implemented in the Jenkinsfile. For example, updating the GitOps repository must be explicitly configured if it is to happen automatically after an image is published.&lt;/p&gt;
&lt;h3&gt;
  
  
  The role of each component
&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;Responsibility&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;Stores application source code and deployment manifests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins&lt;/td&gt;
&lt;td&gt;Orchestrates build, test, and image publication stages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Java and Spring Boot&lt;/td&gt;
&lt;td&gt;Provide the application runtime and framework&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maven&lt;/td&gt;
&lt;td&gt;Compiles, tests, and packages the application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;Packages the application and its runtime dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker Hub&lt;/td&gt;
&lt;td&gt;Stores and distributes container images&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;Schedules and manages application Pods&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Minikube&lt;/td&gt;
&lt;td&gt;Provides a local Kubernetes environment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ArgoCD&lt;/td&gt;
&lt;td&gt;Reconciles Kubernetes resources with Git-defined desired state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kustomize&lt;/td&gt;
&lt;td&gt;Organises and renders Kubernetes configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SonarQube&lt;/td&gt;
&lt;td&gt;Optional code-quality analysis when configured in the pipeline&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  3. Repository Structure and Separation of Responsibilities
&lt;/h2&gt;

&lt;p&gt;I used two GitHub repositories to separate application development from deployment configuration.&lt;/p&gt;
&lt;h3&gt;
  
  
  3.1 Application source repository
&lt;/h3&gt;

&lt;p&gt;Repository: &lt;code&gt;platform-cicd-spring-boot-demo-source&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;URL - &lt;a href="https://github.com/Jahmeeu-Cloud/platform-cicd-spring-boot-demo-source" rel="noopener noreferrer"&gt;https://github.com/Jahmeeu-Cloud/platform-cicd-spring-boot-demo-source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This repository holds the Spring Boot application and its Maven build configuration.&lt;/p&gt;

&lt;p&gt;A typical structure looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-cicd-spring-boot-demo-source/
├── spring-boot-app/
│   ├── pom.xml
│   ├── mvnw
│   ├── .mvn/
│   └── src/
│       ├── main/
│       │   ├── java/
│       │   └── resources/
│       └── test/
├── Dockerfile
├── Jenkinsfile
├── .gitignore
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a recommended structure, not a claim that every file shown was present in the original repository.&lt;/p&gt;

&lt;p&gt;The source repository should contain the code required to build the application. Generated output, credentials, and environment-specific secrets should not be committed.&lt;/p&gt;

&lt;p&gt;For example, Maven's &lt;code&gt;target/&lt;/code&gt; directory contains generated build artifacts. It is normally excluded from Git.&lt;/p&gt;

&lt;p&gt;A suitable &lt;code&gt;.gitignore&lt;/code&gt; could include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;target/
*.log
.env
.env.*
!.env.example
.idea/
.vscode/
.DS_Store
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact exclusions should match the project's development tools and build process.&lt;/p&gt;

&lt;h3&gt;
  
  
  3.2 GitOps configuration repository
&lt;/h3&gt;

&lt;p&gt;Repository: &lt;code&gt;platform-cicd-gitops-config&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;URL: &lt;a href="https://github.com/Jahmeeu-Cloud/platform-cicd-gitops-config" rel="noopener noreferrer"&gt;https://github.com/Jahmeeu-Cloud/platform-cicd-gitops-config&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This repository contains the desired Kubernetes configuration.&lt;/p&gt;

&lt;p&gt;The confirmed base directory is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-cicd-gitops-config/
└── apps/
    └── spring-boot-app/
        └── base/
            ├── deployment.yaml
            ├── service.yaml
            └── kustomization.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Deployment declares how the application should run. The Service provides a stable network endpoint. The Kustomize file identifies the manifests that belong to the application configuration.&lt;/p&gt;

&lt;p&gt;The main principle is that the GitOps repository should describe the desired deployment state rather than merely store commands that someone runs manually.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why keep the repositories separate?
&lt;/h3&gt;

&lt;p&gt;This separation makes it easier to review application changes independently from deployment changes.&lt;/p&gt;

&lt;p&gt;It also creates a clear path for future environment promotion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers change application code.&lt;/li&gt;
&lt;li&gt;Jenkins validates and packages the code.&lt;/li&gt;
&lt;li&gt;A versioned image is published.&lt;/li&gt;
&lt;li&gt;The deployment configuration references that image.&lt;/li&gt;
&lt;li&gt;ArgoCD reconciles the cluster with the approved configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In a larger platform, the same approach can support separate development, staging, and production configurations.&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%2Fqdo2a9fd4fljx3d0gzkp.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%2Fqdo2a9fd4fljx3d0gzkp.png" alt=" " width="800" height="353"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Jahmeeu-Cloud/platform-cicd-spring-boot-demo-source/tree/main/spring-boot-app" rel="noopener noreferrer"&gt;&lt;strong&gt;[SCREENSHOT: GITHUB SOURCE REPOSITORY HERE]&lt;/strong&gt;&lt;br&gt;
&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The source repository contains the application and build-related files.&lt;/em&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%2Fp3386a2qndmptewbmmb7.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%2Fp3386a2qndmptewbmmb7.png" alt=" " width="799" height="325"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/Jahmeeu-Cloud/platform-cicd-gitops-config/tree/main/apps/spring-boot-app/base" rel="noopener noreferrer"&gt;&lt;strong&gt;[SCREENSHOT - GITHUB GITOPS REPOSITORY HERE]&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The GitOps repository stores the Kubernetes manifests used to define the desired application state.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  4. Understanding the Environment Before Starting
&lt;/h2&gt;

&lt;p&gt;Before troubleshooting a CI/CD pipeline, it is useful to understand where each component is running.&lt;/p&gt;

&lt;p&gt;In this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Jenkins ran on AWS EC2.&lt;/li&gt;
&lt;li&gt;The Kubernetes cluster ran locally through Minikube.&lt;/li&gt;
&lt;li&gt;ArgoCD ran inside the Minikube cluster.&lt;/li&gt;
&lt;li&gt;The application image was stored in Docker Hub.&lt;/li&gt;
&lt;li&gt;GitHub stored both the source code and the deployment configuration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This topology has an important implication: Jenkins does not automatically have network access to the local Kubernetes API.&lt;/p&gt;

&lt;p&gt;A common mistake is to install &lt;code&gt;kubectl&lt;/code&gt; on a remote Jenkins server and assume that this gives it access to a local cluster. It does not. The CLI needs a valid cluster endpoint, credentials, and network connectivity.&lt;/p&gt;

&lt;p&gt;In a GitOps architecture, Jenkins can publish the image and update the deployment configuration in Git, while ArgoCD performs the cluster-side reconciliation.&lt;/p&gt;

&lt;p&gt;This avoids requiring the Jenkins server to connect directly to the local cluster.&lt;/p&gt;
&lt;h3&gt;
  
  
  4.1 Check Jenkins
&lt;/h3&gt;

&lt;p&gt;On the EC2 instance:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;systemctl status jenkins
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the service is not 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="nb"&gt;sudo &lt;/span&gt;journalctl &lt;span class="nt"&gt;-u&lt;/span&gt; jenkins &lt;span class="nt"&gt;-n&lt;/span&gt; 100 &lt;span class="nt"&gt;--no-pager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the Java runtime if the Jenkins service reports startup or compatibility problems:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The Java version must be compatible with the installed Jenkins release. Check the official Jenkins documentation for the requirements of the specific version being used.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.2 Check Docker
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker &lt;span class="nt"&gt;--version&lt;/span&gt;
docker info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These commands confirm that the Docker client is installed and can communicate with the daemon under the current user's permissions.&lt;/p&gt;

&lt;p&gt;When Jenkins builds Docker images, test access using the actual Jenkins service account where appropriate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo&lt;/span&gt; &lt;span class="nt"&gt;-u&lt;/span&gt; jenkins docker info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command assumes a Linux installation with a service account named &lt;code&gt;jenkins&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Do not automatically grant broad privileges to fix an access problem. Membership in the Docker group can effectively provide root-level access to the host. Investigate the specific error and choose a build architecture appropriate to the security requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  4.3 Check Minikube and Kubernetes
&lt;/h3&gt;

&lt;p&gt;Run these commands on the machine where Minikube is installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube status
kubectl cluster-info
kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Expected results include a running Minikube cluster, a reachable Kubernetes API, and a node reporting &lt;code&gt;Ready&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F30vqsyqab4f9v70kich0.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%2F30vqsyqab4f9v70kich0.png" alt=" " width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - MINIKUBE AND KUBERNETES STATUS HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The exact output varies by Kubernetes and Minikube version.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. How the CI/CD and GitOps Process Works
&lt;/h2&gt;

&lt;p&gt;The complete delivery process has several logical stages.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 1: Source checkout
&lt;/h3&gt;

&lt;p&gt;Jenkins retrieves the application source code from GitHub.&lt;/p&gt;

&lt;p&gt;A checkout stage might look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;stage&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Checkout'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;steps&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;git&lt;/span&gt; &lt;span class="nl"&gt;branch:&lt;/span&gt; &lt;span class="s1"&gt;'main'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
            &lt;span class="nl"&gt;url:&lt;/span&gt; &lt;span class="s1"&gt;'https://github.com/OWNER/REPOSITORY.git'&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;OWNER/REPOSITORY&lt;/code&gt; with the actual repository path.&lt;/p&gt;

&lt;p&gt;For a private repository, Jenkins needs a suitable credential. For a public repository, a read-only checkout may not require authentication.&lt;/p&gt;

&lt;p&gt;The purpose of checkout is to ensure that the build uses a known source revision rather than files copied manually to the Jenkins server.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 2: Build and test
&lt;/h3&gt;

&lt;p&gt;For a Maven-based Spring Boot application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn clean verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;clean&lt;/code&gt; goal removes previous build output. The &lt;code&gt;verify&lt;/code&gt; lifecycle phase runs the configured checks leading up to verification.&lt;/p&gt;

&lt;p&gt;A successful result means the build and configured verification steps passed. It does not prove that the application has no defects.&lt;/p&gt;

&lt;p&gt;If the project uses the Maven Wrapper, prefer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./mvnw clean verify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wrapper helps the project use the intended Maven version, subject to the availability of a compatible Java runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 3: Optional code analysis
&lt;/h3&gt;

&lt;p&gt;SonarQube can be included to assess code quality and identify potential issues.&lt;/p&gt;

&lt;p&gt;An illustrative command is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn clean verify sonar:sonar &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dsonar&lt;/span&gt;.host.url&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SONAR_HOST_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-Dsonar&lt;/span&gt;.token&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SONAR_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This requires a correctly configured SonarQube server, compatible scanner configuration, and a valid token.&lt;/p&gt;

&lt;p&gt;Do not add this stage to a pipeline until the scanner, server URL, and credentials are configured. A code-quality stage is optional in the reference implementation and should only be described as completed if it was actually executed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 4: Build the Docker image
&lt;/h3&gt;

&lt;p&gt;A Docker image packages the application into a standard format that can be stored and run in compatible container environments.&lt;/p&gt;

&lt;p&gt;A simple example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; horlacloud/spring-boot-demo:BUILD_TAG &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;BUILD_TAG&lt;/code&gt; with a valid version identifier.&lt;/p&gt;

&lt;p&gt;For repeatable releases, use a unique build number, release version, or commit-based tag rather than depending only on &lt;code&gt;latest&lt;/code&gt;.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-t&lt;/span&gt; horlacloud/spring-boot-demo:1.0.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example assumes the Dockerfile is in the current build context. Adjust the path if it is stored elsewhere.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 5: Publish the image
&lt;/h3&gt;

&lt;p&gt;After building the image, Jenkins authenticates to Docker Hub and pushes it.&lt;/p&gt;

&lt;p&gt;A typical manual command sequence is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker login
docker push horlacloud/spring-boot-demo:1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For an automated pipeline, credentials should be supplied securely through Jenkins Credentials rather than typed into a pipeline or stored in the source repository.&lt;/p&gt;

&lt;p&gt;A successful push means the registry accepted the image. It does not prove that Kubernetes can pull it or that the application will start correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 6: Update deployment configuration
&lt;/h3&gt;

&lt;p&gt;The GitOps configuration must reference the intended image.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&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;spring-boot-app&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;horlacloud/spring-boot-demo:1.0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Jenkins is expected to update this value automatically, the Jenkinsfile needs an explicit step to modify the GitOps repository and push the change using an appropriately restricted GitHub credential.&lt;/p&gt;

&lt;p&gt;Publishing an image does not automatically update Kubernetes unless the delivery design includes an image-update mechanism or a corresponding configuration change.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stage 7: ArgoCD reconciliation
&lt;/h3&gt;

&lt;p&gt;ArgoCD monitors the configured Git repository and path.&lt;/p&gt;

&lt;p&gt;When a new desired configuration is committed, ArgoCD can detect the difference and synchronise the managed Kubernetes resources according to its application configuration.&lt;/p&gt;

&lt;p&gt;This is the GitOps part of the workflow.&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%2Fok0weg1sc2hqi89urvw8.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%2Fok0weg1sc2hqi89urvw8.png" alt=" " width="800" height="315"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Feqgubxola0zpnkslh94j.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%2Feqgubxola0zpnkslh94j.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - JENKINS PIPELINE STAGES HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhrrceeu5xv6ix38l98lk.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%2Fhrrceeu5xv6ix38l98lk.png" alt=" " width="800" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - SUCCESSFUL DOCKER HUB IMAGE PUBLICATION HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The build process packages the application into a container image that can be referenced by Kubernetes deployment configuration.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The Jenkins Credential Error That Took Hours to Resolve
&lt;/h2&gt;

&lt;p&gt;One of the most valuable lessons from this project came from a Jenkins credential lookup failure.&lt;/p&gt;

&lt;p&gt;The error was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ERROR: Could not find credentials entry with ID 'github-credentials'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important detail is the wording: Jenkins could not find the credential entry with the requested ID.&lt;/p&gt;

&lt;p&gt;This is different from a rejected token or an access-denied response.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 What is a credential ID?
&lt;/h3&gt;

&lt;p&gt;Jenkins stores credentials separately from the pipeline source code. Each saved credential has an identifier.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="nl"&gt;credentialsId:&lt;/span&gt; &lt;span class="s1"&gt;'github-credentials'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, &lt;code&gt;github-credentials&lt;/code&gt; is the credential ID. It is not the token itself.&lt;/p&gt;

&lt;p&gt;If the saved credential is named &lt;code&gt;github-token&lt;/code&gt;, referencing &lt;code&gt;github-credentials&lt;/code&gt; will not work unless a credential with that exact ID exists in the relevant credential store and scope.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 Troubleshooting the problem
&lt;/h3&gt;

&lt;p&gt;I approached the error by focusing on the stage that failed instead of repeatedly rebuilding the application.&lt;/p&gt;

&lt;p&gt;The checks to perform are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Identify the exact failed stage in the Jenkins console.&lt;/li&gt;
&lt;li&gt;Inspect the credential ID referenced by the Jenkinsfile.&lt;/li&gt;
&lt;li&gt;Open Jenkins Credentials and locate the corresponding entry.&lt;/li&gt;
&lt;li&gt;Confirm the credential's type.&lt;/li&gt;
&lt;li&gt;Check the credential's scope and availability to the job.&lt;/li&gt;
&lt;li&gt;Verify that the credential has the permissions required for the GitHub operation.&lt;/li&gt;
&lt;li&gt;Confirm that the repository URL and branch are correct.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The exact credential ID must match the identifier used in the Jenkinsfile.&lt;/p&gt;

&lt;p&gt;The credential type must also match the pipeline binding. A Secret text credential is not the same as a username/password credential or an SSH private key.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Example: username and token
&lt;/h3&gt;

&lt;p&gt;If the credential is stored as a username/password pair, an illustrative Jenkins binding is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;withCredentials&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;usernamePassword&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;credentialsId:&lt;/span&gt; &lt;span class="s1"&gt;'github-credentials'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;usernameVariable:&lt;/span&gt; &lt;span class="s1"&gt;'GIT_USERNAME'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;passwordVariable:&lt;/span&gt; &lt;span class="s1"&gt;'GIT_TOKEN'&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'''
        set +x
        # Perform the authenticated operation here.
    '''&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The ID and credential type must match the saved Jenkins configuration.&lt;/p&gt;

&lt;p&gt;For a Secret text credential, the binding would instead look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;withCredentials&lt;/span&gt;&lt;span class="o"&gt;([&lt;/span&gt;
    &lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="nl"&gt;credentialsId:&lt;/span&gt; &lt;span class="s1"&gt;'github-token'&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
        &lt;span class="nl"&gt;variable:&lt;/span&gt; &lt;span class="s1"&gt;'GIT_TOKEN'&lt;/span&gt;
    &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;])&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sh&lt;/span&gt; &lt;span class="s1"&gt;'''
        set +x
        # Use the token without printing it.
    '''&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are reference patterns, not the exact final code from my environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.4 Why secrets should not be stored in the Jenkinsfile
&lt;/h3&gt;

&lt;p&gt;A Jenkinsfile is normally stored in version control. Hardcoding a token into it creates a risk of exposing that token through Git history, logs, pull requests, or copied source code.&lt;/p&gt;

&lt;p&gt;The safer approach is to store secrets in Jenkins Credentials and reference them by ID.&lt;/p&gt;

&lt;p&gt;Use narrowly scoped credentials, protect pipeline changes, and rotate exposed secrets promptly.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.5 Three similar errors that require different fixes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Error&lt;/th&gt;
&lt;th&gt;Likely meaning&lt;/th&gt;
&lt;th&gt;First check&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Credential not found&lt;/td&gt;
&lt;td&gt;Jenkins cannot resolve the referenced ID&lt;/td&gt;
&lt;td&gt;Credential ID and scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authentication failed&lt;/td&gt;
&lt;td&gt;The authentication method or secret was rejected&lt;/td&gt;
&lt;td&gt;Token validity, username, credential type&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission denied&lt;/td&gt;
&lt;td&gt;The identity lacks access to the requested resource&lt;/td&gt;
&lt;td&gt;Repository permissions and token scope&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This distinction saves time because each error points to a different part of the configuration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fbglslrfh4hzbcgqzgb8s.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%2Fbglslrfh4hzbcgqzgb8s.png" alt=" " width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - THE SUCCESSFUL PIPELINE BUILD HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;By the 20th CI/CD pipeline build, I had worked through the integration problems and achieved a successful pipeline build.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Building a Spring Boot Application Container
&lt;/h2&gt;

&lt;p&gt;A container image needs a Dockerfile that explains how the application is built or how its packaged artifact is executed.&lt;/p&gt;

&lt;p&gt;There are two common approaches.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option A: Build the JAR before creating the image
&lt;/h3&gt;

&lt;p&gt;If Maven has already generated the JAR, a simple Dockerfile can copy that artifact into a Java runtime image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eclipse-temurin:21-jre&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; target/*.jar app.jar&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; 10001&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "/app/app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an illustrative example. The Java runtime version must match the application's requirements, and the build process must ensure that exactly the intended JAR is copied.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;EXPOSE&lt;/code&gt; instruction documents the expected container port; it does not publish the port by itself.&lt;/p&gt;

&lt;p&gt;The non-root user improves the default security posture, provided the application can run with the required filesystem permissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option B: Use a multi-stage Docker build
&lt;/h3&gt;

&lt;p&gt;A multi-stage Dockerfile can compile the application in a build image and copy only the packaged artifact into the runtime image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;maven:3.9-eclipse-temurin-21&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /workspace&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; pom.xml .&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; src ./src&lt;/span&gt;

&lt;span class="k"&gt;RUN &lt;/span&gt;mvn &lt;span class="nt"&gt;-B&lt;/span&gt; clean package &lt;span class="nt"&gt;-DskipTests&lt;/span&gt;

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; eclipse-temurin:21-jre&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /workspace/target/*.jar app.jar&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; 10001&lt;/span&gt;

&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "/app/app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example assumes the Maven project is at the Docker build context root and produces one intended executable JAR.&lt;/p&gt;

&lt;p&gt;Notice that this sample skips tests during packaging. In a complete CI/CD workflow, tests should run in a dedicated stage before image publication, and the pipeline should stop if they fail. Avoid using &lt;code&gt;-DskipTests&lt;/code&gt; as a replacement for automated testing.&lt;/p&gt;

&lt;p&gt;The runtime image does not need the entire Maven build environment, which can reduce the amount of tooling included in the final container.&lt;/p&gt;

&lt;h3&gt;
  
  
  Build and test locally
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn clean verify
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; spring-boot-app:local &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 8080:8080 spring-boot-app:local
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, in another terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://localhost:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use an actual application route if the root path is not defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Deploying the Application with Kubernetes
&lt;/h2&gt;

&lt;p&gt;Kubernetes manages the desired state of containerised workloads. It schedules Pods, monitors workload state, and attempts to maintain the replica count specified in the Deployment.&lt;/p&gt;

&lt;p&gt;The project used a Deployment and a Service for the Spring Boot application.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.1 Deployment manifest
&lt;/h3&gt;

&lt;p&gt;The confirmed GitOps configuration declares three replicas and the image &lt;code&gt;horlacloud/spring-boot-demo:20&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;An illustrative Deployment is:&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;spring-boot-app&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;spring-boot-app&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;spring-boot-app&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;spring-boot-app&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;spring-boot-app&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;horlacloud/spring-boot-demo:20&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&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;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This shows the core configuration. A production-oriented Deployment should normally also define appropriate probes, resource requests and limits, and a security context.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;replicas&lt;/code&gt; field is the desired number of Pods. Kubernetes attempts to maintain that number, but scheduling constraints, image pull failures, application crashes, and insufficient capacity can prevent all replicas from becoming ready.&lt;/p&gt;

&lt;p&gt;The selector must match the labels on the Pod template. If the selector and template labels do not match, the Deployment will be invalid or fail to manage the intended Pods.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.2 Service manifest
&lt;/h3&gt;

&lt;p&gt;A Service provides a stable endpoint for matching Pods.&lt;/p&gt;

&lt;p&gt;The project's Service is named &lt;code&gt;spring-boot-app-service&lt;/code&gt;. A representative configuration is:&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;spring-boot-app-service&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&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;spring-boot-app&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&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;8080&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;port: 80&lt;/code&gt; is the Service port.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;targetPort: 8080&lt;/code&gt; is the port on the application container.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;type: NodePort&lt;/code&gt; exposes the Service through a port on the Kubernetes node.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;selector&lt;/code&gt; identifies the Pods that should receive traffic.&lt;/p&gt;

&lt;p&gt;A Service can exist even when it has no ready endpoints. That is why it is important to inspect both the Service and the Pods behind it.&lt;/p&gt;

&lt;h3&gt;
  
  
  8.3 Validate the resources
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get deployments
kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide
kubectl get services
kubectl get endpoints spring-boot-app-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect the Deployment in more detail:&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 deployment spring-boot-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect an individual 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 POD_NAME
kubectl logs POD_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;POD_NAME&lt;/code&gt; with the name returned by &lt;code&gt;kubectl get pods&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F23redthgohs1b7uyw74c.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%2F23redthgohs1b7uyw74c.png" alt=" " width="799" height="309"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - KUBERNETES DEPLOYMENT AND POD STATUS HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fg85kkwssy1h5krsspzmm.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%2Fg85kkwssy1h5krsspzmm.png" alt=" " width="800" height="219"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - KUBERNETES SERVICE AND ENDPOINTS HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Kubernetes was managing the application replicas and exposing them through a stable Service.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  9. ConfigMaps, Secrets, and Application Configuration
&lt;/h2&gt;

&lt;p&gt;Applications often need configuration that should not be embedded directly into the container image.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Application mode or environment name.&lt;/li&gt;
&lt;li&gt;Log level.&lt;/li&gt;
&lt;li&gt;API endpoint URLs.&lt;/li&gt;
&lt;li&gt;Database connection settings.&lt;/li&gt;
&lt;li&gt;Authentication credentials.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Kubernetes provides ConfigMaps for non-sensitive configuration and Secrets for sensitive values.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.1 ConfigMap example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;spring-boot-config&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;APP_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;development"&lt;/span&gt;
  &lt;span class="na"&gt;LOG_LEVEL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;INFO"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Deployment can expose these values to the container:&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;envFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;configMapRef&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;spring-boot-config&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This belongs under the container specification.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.2 Secret example
&lt;/h3&gt;

&lt;p&gt;A Secret can hold sensitive configuration such as a database password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;database-credentials&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app_user&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_A_SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a template only. Replace the placeholder using an appropriate secret-management process.&lt;/p&gt;

&lt;p&gt;Do not commit real passwords, access tokens, or private keys to Git. Kubernetes Secret values are not automatically safe simply because they are stored in a Secret object; access controls and encryption-at-rest configuration also matter.&lt;/p&gt;

&lt;p&gt;For production environments, consider a suitable external secret-management integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.3 Why configuration should be separate
&lt;/h3&gt;

&lt;p&gt;Separating configuration from application code makes it easier to promote the same image across environments without rebuilding it for every setting change.&lt;/p&gt;

&lt;p&gt;It also reduces the risk of accidentally baking environment-specific values into a reusable image.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Persistent Volumes and Persistent Volume Claims
&lt;/h2&gt;

&lt;p&gt;Persistent storage deserves special attention because container storage and application data have different lifecycles.&lt;/p&gt;

&lt;p&gt;A container's writable filesystem is generally not intended to be the durable home for important data. If a container is replaced, its writable layer may be lost.&lt;/p&gt;

&lt;p&gt;Kubernetes persistent storage provides a way to manage data independently of an individual container's lifecycle.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.1 Understanding the storage components
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;PersistentVolume (PV):&lt;/strong&gt; Represents a piece of storage available to a Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PersistentVolumeClaim (PVC):&lt;/strong&gt; A request for storage made by an application or workload.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;StorageClass:&lt;/strong&gt; Defines how storage can be provisioned. Depending on the cluster, it may support dynamic provisioning so that a PVC can trigger creation of a volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pod volume mount:&lt;/strong&gt; Connects the claimed storage to a path inside a container.&lt;/p&gt;

&lt;p&gt;The relationship is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application Pod
      |
      v
  Volume Mount
      |
      v
 PersistentVolumeClaim
      |
      v
 PersistentVolume
      |
      v
Underlying Storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When dynamic provisioning is enabled, a PVC may trigger the creation of a PV through its StorageClass.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.2 When should persistent storage be used?
&lt;/h3&gt;

&lt;p&gt;Persistent storage may be appropriate for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Database data directories.&lt;/li&gt;
&lt;li&gt;Application files that must survive Pod replacement.&lt;/li&gt;
&lt;li&gt;Durable uploads or other persistent application data.&lt;/li&gt;
&lt;li&gt;Workloads that explicitly require filesystem persistence.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is generally not necessary for every application container.&lt;/p&gt;

&lt;p&gt;For example, a stateless Spring Boot web application may not need a PVC if it stores no durable files locally and uses an external database or object storage.&lt;/p&gt;

&lt;p&gt;The right storage design depends on the application's data requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.3 Inspect available StorageClasses
&lt;/h3&gt;

&lt;p&gt;Before creating a PVC, check the cluster:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The available StorageClasses depend on the Kubernetes environment and installed storage provisioners.&lt;/p&gt;

&lt;p&gt;A Minikube installation may provide a default StorageClass, but this should be checked rather than assumed.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.4 PVC example
&lt;/h3&gt;

&lt;p&gt;This example requests 1 GiB of persistent 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;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;PersistentVolumeClaim&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;app-data&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;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="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;1Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example leaves &lt;code&gt;storageClassName&lt;/code&gt; unspecified so that the cluster's default StorageClass can be used when one is configured.&lt;/p&gt;

&lt;p&gt;The claim may remain &lt;code&gt;Pending&lt;/code&gt; if no suitable StorageClass or matching volume is available.&lt;/p&gt;

&lt;p&gt;The access mode &lt;code&gt;ReadWriteOnce&lt;/code&gt; generally allows a volume to be mounted read-write by a single node at a time; it does not necessarily mean only one Pod can use it. Actual access behaviour depends on the storage implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.5 Mount the PVC into a Pod
&lt;/h3&gt;

&lt;p&gt;The claim can be mounted into a container through a Pod or Deployment specification.&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;storage-demo&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;1&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;storage-demo&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;storage-demo&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;storage-demo&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;busybox:1.36&lt;/span&gt;
          &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;while&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;true;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;do&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/data/timestamps.txt;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;sleep&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;30;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;done"&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;app-data&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;/data&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;app-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;app-data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a storage demonstration, not part of the confirmed Spring Boot deployment. It writes timestamps to a file so that persistence can be tested.&lt;/p&gt;

&lt;p&gt;Apply the example only in a suitable test namespace:&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; pvc.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; storage-demo.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the claim and Pod:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Once the Pod is running, inspect the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec &lt;/span&gt;deploy/storage-demo &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nb"&gt;cat&lt;/span&gt; /data/timestamps.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Delete the Pod without deleting the PVC, wait for the replacement Pod, and check the file again. If the same claim is successfully remounted, the file should still contain the previously written data.&lt;/p&gt;

&lt;p&gt;The exact behaviour depends on the provisioner and storage configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.6 What happens when a PVC is deleted?
&lt;/h3&gt;

&lt;p&gt;Deleting a PVC can affect the underlying storage depending on the PV reclaim policy and provisioner.&lt;/p&gt;

&lt;p&gt;Common reclaim policies include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Retain&lt;/code&gt;: The volume is retained for manual recovery or reuse.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Delete&lt;/code&gt;: The underlying storage may be deleted when the claim is released.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Never assume that deleting a PVC is harmless.&lt;/p&gt;

&lt;p&gt;Before testing cleanup, inspect the PV and its reclaim policy:&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 pv
kubectl describe pv PV_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;PV_NAME&lt;/code&gt; with the actual volume name.&lt;/p&gt;

&lt;p&gt;Persistent storage is not a substitute for backups. A durable volume may survive Pod replacement while still being vulnerable to accidental deletion, corruption, or infrastructure failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  11. Extending the Project to a Frontend and Backend
&lt;/h2&gt;

&lt;p&gt;The original confirmed deployment is a Spring Boot application. If I extend the capstone to a frontend and backend, I would treat them as separate workloads with clearly defined network paths.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser
   |
   v
Frontend Service
   |
   v
Frontend Pods
   |
   v
Backend Service
   |
   v
Backend Pods
   |
   v
Database Service
   |
   v
Database Pods or External Database
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a reference architecture for a future extension.&lt;/p&gt;

&lt;p&gt;The frontend could be a React application served through Nginx, while the backend could be a Spring Boot API or a Node.js service. The backend technology depends on the actual application.&lt;/p&gt;

&lt;h3&gt;
  
  
  11.1 Frontend Dockerfile example
&lt;/h3&gt;

&lt;p&gt;For a React application built with Vite:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginx:alpine&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist /usr/share/nginx/html&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example assumes the project uses &lt;code&gt;npm&lt;/code&gt;, has a valid lockfile, and generates its production build in &lt;code&gt;dist/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For a single-page application, Nginx may also need a custom configuration to route client-side paths back to &lt;code&gt;index.html&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  11.2 Frontend Kubernetes Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;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;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;example/frontend:1.0.0&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;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;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;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;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;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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the example image with an image that actually exists in your registry.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ClusterIP&lt;/code&gt; Service is reachable inside the cluster. External access can be provided through an Ingress, Gateway, LoadBalancer, or a local development mechanism appropriate to the environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  11.3 Backend Deployment and Service
&lt;/h3&gt;

&lt;p&gt;A generic backend might look like this:&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;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;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;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;example/backend:1.0.0&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;8080&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;/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;8080&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;10&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;/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;8080&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;20&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;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;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;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;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&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;8080&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a generic example. The backend must actually expose &lt;code&gt;/health&lt;/code&gt; and return an appropriate successful response. Spring Boot applications may use a different health endpoint depending on their configuration.&lt;/p&gt;

&lt;p&gt;The readiness probe determines whether a Pod should receive Service traffic. The liveness probe helps Kubernetes detect certain application states that require a container restart. They serve different purposes, so they should not automatically use the same endpoint in every application.&lt;/p&gt;

&lt;h3&gt;
  
  
  11.4 How should the frontend reach the backend?
&lt;/h3&gt;

&lt;p&gt;Inside Kubernetes, the frontend can call a backend Service using its DNS name, such as:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;However, a browser-based React application executes on the user's device, not inside the frontend container. A URL such as &lt;code&gt;http://backend:8080&lt;/code&gt; will not normally resolve in the user's browser.&lt;/p&gt;

&lt;p&gt;For browser-based applications, the frontend usually needs a publicly reachable API URL or an Ingress/reverse-proxy arrangement that routes requests to the backend.&lt;/p&gt;

&lt;p&gt;The correct approach depends on whether the request originates from browser JavaScript or from a server-side process.&lt;/p&gt;

&lt;h3&gt;
  
  
  11.5 How would I connect this to GitOps?
&lt;/h3&gt;

&lt;p&gt;The frontend and backend manifests could be organised under one application directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/
└── web-platform/
    ├── base/
    │   ├── frontend.yaml
    │   ├── backend.yaml
    │   └── kustomization.yaml
    └── overlays/
        ├── staging/
        └── production/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base would contain shared configuration. Overlays could define environment-specific image tags, replica counts, resources, and other differences.&lt;/p&gt;

&lt;p&gt;This layout should be introduced only when the additional workloads have actually been created and tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Databases and Stateful Workloads
&lt;/h2&gt;

&lt;p&gt;Databases require additional planning because application availability and data durability are different concerns.&lt;/p&gt;

&lt;p&gt;A Kubernetes Deployment is commonly used for stateless services. A StatefulSet may be appropriate for a workload that needs stable Pod identities and persistent storage.&lt;/p&gt;

&lt;p&gt;A StatefulSet does not automatically make a database highly available, provide safe backups, or configure replication. Those behaviours depend on the database and its operational design.&lt;/p&gt;

&lt;p&gt;For a learning environment, a database could use a PVC for its data directory. In a production system, I would evaluate whether a managed database service is more appropriate.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important database considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Persistent storage must be provisioned and mounted correctly.&lt;/li&gt;
&lt;li&gt;Database credentials should not be committed to Git.&lt;/li&gt;
&lt;li&gt;The database should have appropriate readiness and health checks.&lt;/li&gt;
&lt;li&gt;Application connections should use the correct Service name and port.&lt;/li&gt;
&lt;li&gt;Backups should be scheduled and restore procedures tested.&lt;/li&gt;
&lt;li&gt;Database schema migrations should be managed deliberately.&lt;/li&gt;
&lt;li&gt;Storage capacity and performance should be monitored.&lt;/li&gt;
&lt;li&gt;A storage snapshot alone should not be assumed to be a consistent database backup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The application should also handle database startup and temporary connectivity failures without entering an uncontrolled restart loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  13. GitOps with ArgoCD
&lt;/h2&gt;

&lt;p&gt;GitOps is the practice of using a version-controlled repository to declare the desired state of an environment and a controller to reconcile the actual environment with that desired state.&lt;/p&gt;

&lt;p&gt;In this project, ArgoCD monitored:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Repository:
https://github.com/Jahmeeu-Cloud/platform-cicd-gitops-config.git

Path:
apps/spring-boot-app/base

Branch:
main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application was configured to deploy into the Kubernetes &lt;code&gt;default&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  13.1 Inspect the ArgoCD application
&lt;/h3&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
kubectl get application spring-boot-app &lt;span class="nt"&gt;-n&lt;/span&gt; argocd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected successful state was &lt;code&gt;Synced&lt;/code&gt; and &lt;code&gt;Healthy&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Synced&lt;/code&gt; indicates that the managed resources match the desired configuration according to ArgoCD's comparison.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Healthy&lt;/code&gt; indicates that ArgoCD considers the resources healthy under its health assessment rules.&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%2F0xg50twup2t9xcgnth1t.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%2F0xg50twup2t9xcgnth1t.png" alt=" " width="800" height="70"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Neither status guarantees that every business function works correctly. An HTTP request and application-specific tests provide additional evidence.&lt;/p&gt;

&lt;h3&gt;
  
  
  13.2 Example ArgoCD Application manifest
&lt;/h3&gt;

&lt;p&gt;The following is an illustrative manifest for the repository and path used in this project:&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;spring-boot-app&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;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/Jahmeeu-Cloud/platform-cicd-gitops-config.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;main&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;apps/spring-boot-app/base&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;default&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a reference configuration. Use the existing application manifest rather than replacing it blindly.&lt;/p&gt;

&lt;p&gt;Automatic pruning can delete resources that are removed from the desired Git configuration. Review its implications before enabling it for important workloads.&lt;/p&gt;

&lt;h3&gt;
  
  
  13.3 Validate the manifests before committing
&lt;/h3&gt;

&lt;p&gt;From the GitOps repository:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl kustomize apps/spring-boot-app/base
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This renders the Kustomize configuration. It is a useful early check that the directory can be processed.&lt;/p&gt;

&lt;p&gt;After a change, inspect the rendered output and review the diff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
git diff
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then commit and push the intended files:&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 apps/spring-boot-app/base
git commit &lt;span class="nt"&gt;-m&lt;/span&gt; &lt;span class="s2"&gt;"Update application deployment configuration"&lt;/span&gt;
git push origin main
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run these commands from the GitOps repository and confirm that the correct remote and branch are configured before pushing.&lt;/p&gt;

&lt;h3&gt;
  
  
  13.4 Verify reconciliation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get application spring-boot-app &lt;span class="nt"&gt;-n&lt;/span&gt; argocd
kubectl get deployment spring-boot-app
kubectl get pods
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The aim is to confirm that the Git-defined desired state has been applied and the expected Pods are running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa65t3j8wfe913dc2dahg.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%2Fa65t3j8wfe913dc2dahg.png" alt=" " width="799" height="197"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - ARGOCD APPLICATION STATUS HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Suggested caption: ArgoCD was tracking the GitOps repository and reporting the synchronisation and health of the application.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  14. Testing GitOps Self-Healing
&lt;/h2&gt;

&lt;p&gt;The most useful test in this project was deliberately changing the live Deployment and observing how ArgoCD responded.&lt;/p&gt;

&lt;p&gt;The GitOps configuration declared three replicas.&lt;/p&gt;

&lt;p&gt;I manually changed the live Deployment to one replica:&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 spring-boot-app &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;Then I watched the Deployment:&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 deployment spring-boot-app &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the ArgoCD application was configured for automated synchronisation and self-healing, ArgoCD detected the difference between the live configuration and the desired state in Git.&lt;/p&gt;

&lt;p&gt;The replica count returned to three.&lt;/p&gt;

&lt;h3&gt;
  
  
  What did this prove?
&lt;/h3&gt;

&lt;p&gt;The test demonstrated that the GitOps controller could correct this particular kind of configuration drift.&lt;/p&gt;

&lt;p&gt;The process was:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Git declared three replicas.&lt;/li&gt;
&lt;li&gt;The live Deployment was manually scaled down to one.&lt;/li&gt;
&lt;li&gt;The live state no longer matched the desired state.&lt;/li&gt;
&lt;li&gt;ArgoCD detected the difference.&lt;/li&gt;
&lt;li&gt;Automated reconciliation restored the declared replica count.&lt;/li&gt;
&lt;li&gt;Kubernetes worked towards the desired three-replica state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a practical example of declarative operations.&lt;/p&gt;

&lt;p&gt;It also shows why manual changes to GitOps-managed resources may be temporary. If the desired state remains unchanged in Git, the controller may restore it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does self-healing fix every problem?
&lt;/h3&gt;

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

&lt;p&gt;ArgoCD can reconcile managed resources with the desired configuration, but it cannot automatically solve every application bug, exhausted storage volume, invalid image, external database outage, or cluster networking problem.&lt;/p&gt;

&lt;p&gt;Self-healing is a reconciliation capability, not a replacement for monitoring, testing, and operational troubleshooting.&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%2F5yzrb1hrej3d3zndniq7.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%2F5yzrb1hrej3d3zndniq7.png" alt=" " width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - THE DEPLOYMENT AT ONE REPLICA HERE AND THE DEPLOYMENT RESTORED TO THREE REPLICAS HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Suggested caption: I deliberately changed the live replica count and observed ArgoCD restore the desired state defined in Git.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  15. Verifying the Application End to End
&lt;/h2&gt;

&lt;p&gt;A successful Jenkins build is not the same as a successful application deployment.&lt;/p&gt;

&lt;p&gt;I validated the Kubernetes resources and tested the application endpoint independently.&lt;/p&gt;

&lt;h3&gt;
  
  
  15.1 Check the Deployment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get deployment spring-boot-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect the replica counts and confirm that the expected Pods become ready.&lt;/p&gt;

&lt;h3&gt;
  
  
  15.2 Check the Pods
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;If a Pod is not ready, investigate it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe pod POD_NAME
kubectl logs POD_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;POD_NAME&lt;/code&gt; with the actual Pod name.&lt;/p&gt;

&lt;h3&gt;
  
  
  15.3 Check the Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get service spring-boot-app-service
kubectl get endpoints spring-boot-app-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoints help show whether the Service has selected and is routing to suitable Pods.&lt;/p&gt;

&lt;h3&gt;
  
  
  15.4 Find the current Minikube access address
&lt;/h3&gt;

&lt;p&gt;For a NodePort Service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube ip
kubectl get service spring-boot-app-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the returned IP and NodePort to construct the URL, provided the Minikube networking setup supports direct access that way.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube service spring-boot-app-service &lt;span class="nt"&gt;--url&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command may open a tunnel or return a URL depending on the driver and operating environment. Follow any instructions printed by Minikube.&lt;/p&gt;

&lt;h3&gt;
  
  
  15.5 Send an HTTP request
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://MINIKUBE_IP:NODE_PORT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the placeholders with the actual address and port.&lt;/p&gt;

&lt;p&gt;In the test performed for this project, the application endpoint returned HTTP 200.&lt;/p&gt;

&lt;p&gt;That result confirmed that the tested route responded successfully. It did not, by itself, prove that every application function was correct.&lt;/p&gt;

&lt;h3&gt;
  
  
  What did the end-to-end test validate?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;The Deployment existed.&lt;/li&gt;
&lt;li&gt;Kubernetes created application Pods.&lt;/li&gt;
&lt;li&gt;The Service selected the intended workload.&lt;/li&gt;
&lt;li&gt;The endpoint was reachable through the tested access path.&lt;/li&gt;
&lt;li&gt;The application returned a successful HTTP response.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Together, these checks provided stronger evidence than a successful build result alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  16. Probes, Resource Requests, and Limits
&lt;/h2&gt;

&lt;p&gt;For a more robust deployment, I would extend the Deployment with resource settings and appropriate health probes.&lt;/p&gt;

&lt;p&gt;An illustrative container section is:&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;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;spring-boot-app&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;horlacloud/spring-boot-demo:20&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&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;8080&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;/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;8080&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;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;/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;8080&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;20&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;20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These values are examples, not measurements from the capstone.&lt;/p&gt;

&lt;p&gt;The application must actually expose the configured health endpoint. A Spring Boot application may require an appropriate health endpoint configuration, and the route may differ from &lt;code&gt;/health&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Resource requests influence scheduling. Resource limits constrain how much CPU or memory the container may consume under the configured runtime rules.&lt;/p&gt;

&lt;p&gt;A readiness probe helps determine when a container should receive Service traffic. A liveness probe helps Kubernetes decide whether to restart a container that appears unable to recover.&lt;/p&gt;

&lt;p&gt;Probe configuration must reflect the application's real startup and recovery behaviour. Aggressive probes can make a slow-starting application less reliable rather than more reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  17. Availability, Scaling, and Reliability
&lt;/h2&gt;

&lt;p&gt;These concepts are related, but they are not interchangeable.&lt;/p&gt;

&lt;h3&gt;
  
  
  Availability
&lt;/h3&gt;

&lt;p&gt;Three replicas provide multiple application instances, but three replicas on a single-node local cluster do not provide node-level high availability.&lt;/p&gt;

&lt;p&gt;If the only node fails, all Pods on that node may become unavailable.&lt;/p&gt;

&lt;p&gt;A production design would consider multiple nodes, appropriate failure-domain placement, load balancing, cluster capacity, persistent storage, and recovery procedures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scalability
&lt;/h3&gt;

&lt;p&gt;A Deployment's replica count can be changed manually:&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 spring-boot-app &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Horizontal Pod Autoscaler can be introduced to adjust the number of replicas based on metrics.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl autoscale deployment spring-boot-app &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cpu-percent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;70 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--min&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--max&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an optional extension. It requires a working metrics source and suitable resource requests. It should not be described as implemented unless the HPA was actually created and verified.&lt;/p&gt;

&lt;p&gt;Scaling Pods also does not guarantee greater throughput if the application depends on a saturated database or another bottleneck.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;

&lt;p&gt;Reliability depends on more than the number of replicas. It also depends on application health checks, dependency availability, resource planning, configuration management, observability, and tested recovery procedures.&lt;/p&gt;

&lt;p&gt;GitOps adds a useful form of configuration consistency by making the desired state explicit and version-controlled.&lt;/p&gt;

&lt;h2&gt;
  
  
  18. Troubleshooting Guide
&lt;/h2&gt;

&lt;p&gt;A repeatable troubleshooting approach starts by identifying which layer is failing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jenkins pipeline fails
&lt;/h3&gt;

&lt;p&gt;Inspect the failed stage and the console output.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Question: Did checkout, build, test, image build, or push fail?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If checkout fails, check the repository URL, branch, network connectivity, and credential requirements.&lt;/p&gt;

&lt;p&gt;If Maven fails, inspect the compilation or test output before changing Docker configuration.&lt;/p&gt;

&lt;p&gt;If Docker fails, inspect the Dockerfile, build context, daemon access, and image tag.&lt;/p&gt;

&lt;p&gt;If the GitOps update fails, inspect the GitHub credential ID, credential type, token permissions, branch rules, and remote URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  ImagePullBackOff
&lt;/h3&gt;

&lt;p&gt;Check the Pod events:&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 POD_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Incorrect image name or tag.&lt;/li&gt;
&lt;li&gt;Image not present in the registry.&lt;/li&gt;
&lt;li&gt;Private registry authentication not configured.&lt;/li&gt;
&lt;li&gt;Registry connectivity issues.&lt;/li&gt;
&lt;li&gt;Architecture incompatibility.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not assume that an image was successfully published merely because the local Docker build passed.&lt;/p&gt;

&lt;h3&gt;
  
  
  CrashLoopBackOff
&lt;/h3&gt;

&lt;p&gt;Inspect the container logs and events:&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 POD_NAME
kubectl describe pod POD_NAME
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possible causes include a bad startup command, application configuration problems, missing environment variables, dependency failures, or memory constraints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service has no endpoints
&lt;/h3&gt;

&lt;p&gt;Inspect the Service selector and Pod labels:&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 service spring-boot-app-service
kubectl get pods &lt;span class="nt"&gt;--show-labels&lt;/span&gt;
kubectl get endpoints spring-boot-app-service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Service selector must match the intended Pods. Pods that are not ready may not be included as normal Service endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  PVC remains Pending
&lt;/h3&gt;

&lt;p&gt;Check the claim and StorageClass:&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 pvc app-data
kubectl get storageclass
kubectl get pv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Possible causes include an unavailable provisioner, no matching volume, an unsupported access mode, or insufficient capacity.&lt;/p&gt;

&lt;h3&gt;
  
  
  ArgoCD reports OutOfSync
&lt;/h3&gt;

&lt;p&gt;Inspect the application status and compare the live state with Git.&lt;/p&gt;

&lt;p&gt;Check whether:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The correct repository is configured.&lt;/li&gt;
&lt;li&gt;The expected branch and path are selected.&lt;/li&gt;
&lt;li&gt;The commit was pushed successfully.&lt;/li&gt;
&lt;li&gt;The manifests render correctly.&lt;/li&gt;
&lt;li&gt;Automatic synchronisation is configured.&lt;/li&gt;
&lt;li&gt;The controller can access the repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If manual changes keep being reverted, check whether self-healing is enabled and whether the resource is managed by the ArgoCD application.&lt;/p&gt;

&lt;h3&gt;
  
  
  HTTP request fails
&lt;/h3&gt;

&lt;p&gt;Work from the inside out:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check Pod readiness.&lt;/li&gt;
&lt;li&gt;Inspect application logs.&lt;/li&gt;
&lt;li&gt;Check the Service selector.&lt;/li&gt;
&lt;li&gt;Check Service endpoints.&lt;/li&gt;
&lt;li&gt;Confirm the application listens on the expected container port.&lt;/li&gt;
&lt;li&gt;Confirm the correct NodePort, tunnel, or ingress route.&lt;/li&gt;
&lt;li&gt;Test the correct application path.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process avoids changing several layers at once without knowing which one is responsible.&lt;/p&gt;

&lt;h2&gt;
  
  
  19. Security Practices
&lt;/h2&gt;

&lt;p&gt;The project interacts with several systems that require controlled access.&lt;/p&gt;

&lt;h3&gt;
  
  
  Jenkins
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Restrict administrative access.&lt;/li&gt;
&lt;li&gt;Maintain Jenkins and its plugins.&lt;/li&gt;
&lt;li&gt;Store secrets in Jenkins Credentials.&lt;/li&gt;
&lt;li&gt;Review Jenkinsfile changes.&lt;/li&gt;
&lt;li&gt;Avoid exposing secrets in console output.&lt;/li&gt;
&lt;li&gt;Restrict the privileges available to build agents.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GitHub
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use narrowly scoped credentials.&lt;/li&gt;
&lt;li&gt;Protect important branches.&lt;/li&gt;
&lt;li&gt;Require reviews where appropriate.&lt;/li&gt;
&lt;li&gt;Never commit tokens or passwords.&lt;/li&gt;
&lt;li&gt;Review repository permissions before allowing automated writes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Docker
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Prefer maintained base images.&lt;/li&gt;
&lt;li&gt;Scan images for vulnerabilities where tooling is available.&lt;/li&gt;
&lt;li&gt;Use non-root containers when practical.&lt;/li&gt;
&lt;li&gt;Avoid embedding secrets in images.&lt;/li&gt;
&lt;li&gt;Use traceable image versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Kubernetes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Apply appropriate resource requests and limits.&lt;/li&gt;
&lt;li&gt;Use security contexts.&lt;/li&gt;
&lt;li&gt;Restrict network access where practical.&lt;/li&gt;
&lt;li&gt;Use readiness and liveness probes thoughtfully.&lt;/li&gt;
&lt;li&gt;Protect Secrets through access control and appropriate encryption configuration.&lt;/li&gt;
&lt;li&gt;Limit service-account permissions.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Review changes before promoting them to important environments.&lt;/li&gt;
&lt;li&gt;Understand the effect of automated pruning.&lt;/li&gt;
&lt;li&gt;Protect the branch used by ArgoCD.&lt;/li&gt;
&lt;li&gt;Keep sensitive values out of ordinary deployment manifests.&lt;/li&gt;
&lt;li&gt;Make rollback procedures explicit and testable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These controls are part of the delivery architecture, not optional additions that can be ignored until a security incident occurs.&lt;/p&gt;

&lt;h2&gt;
  
  
  20. Reusability and Future Platform Improvements
&lt;/h2&gt;

&lt;p&gt;One of the main benefits of declarative configuration is the ability to reuse it.&lt;/p&gt;

&lt;p&gt;Kustomize allows a project to maintain a common base and apply environment-specific overlays.&lt;/p&gt;

&lt;p&gt;A possible future layout is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps/
└── spring-boot-app/
    ├── base/
    │   ├── deployment.yaml
    │   ├── service.yaml
    │   └── kustomization.yaml
    └── overlays/
        ├── staging/
        │   └── kustomization.yaml
        └── production/
            └── kustomization.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The base defines shared resources. Overlays can customise image tags, replica counts, and other settings for different environments.&lt;/p&gt;

&lt;p&gt;This is a suggested extension rather than a description of already completed overlays.&lt;/p&gt;

&lt;p&gt;Before committing configuration changes, render the 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 kustomize apps/spring-boot-app/base
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then review the result and the Git diff:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git diff
git status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A reusable deployment pattern should reduce repetitive work while keeping the resulting configuration understandable to the engineers responsible for operating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  21. Version Control, Backups, and Cleanup
&lt;/h2&gt;

&lt;p&gt;Version control records configuration changes, but it is not a complete backup strategy for every kind of project data.&lt;/p&gt;

&lt;p&gt;The source and GitOps repositories provide a record of the files committed at specific revisions.&lt;/p&gt;

&lt;p&gt;The GitOps repository's last confirmed commit during this work was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;dc9e323 Fix deployment YAML and set replicas to three
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That commit recorded a change to the deployment configuration.&lt;/p&gt;

&lt;p&gt;For future maintenance, I would ensure that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The intended files are committed.&lt;/li&gt;
&lt;li&gt;Generated build output is excluded when appropriate.&lt;/li&gt;
&lt;li&gt;The remote URL points to the correct repository.&lt;/li&gt;
&lt;li&gt;The default branch contains the intended configuration.&lt;/li&gt;
&lt;li&gt;Deployment manifests render successfully.&lt;/li&gt;
&lt;li&gt;The deployed image tag corresponds to a published image.&lt;/li&gt;
&lt;li&gt;Credentials remain outside Git.&lt;/li&gt;
&lt;li&gt;Persistent data is backed up separately when required.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before cleaning up a cloud environment, identify which resources belong to the project and whether any contain data that must be retained.&lt;/p&gt;

&lt;p&gt;Before deleting a PVC, inspect its reclaim policy and confirm that important data is backed up.&lt;/p&gt;

&lt;p&gt;Before deleting an AWS resource, verify that it is not shared with another workload or still required by a running service.&lt;/p&gt;

&lt;p&gt;Cleanup should be deliberate, documented, and reversible where practical.&lt;/p&gt;

&lt;h2&gt;
  
  
  22. What I Would Improve Before Calling This Production-Ready
&lt;/h2&gt;

&lt;p&gt;The project was a hands-on learning environment, not a claim of a production-grade platform.&lt;/p&gt;

&lt;p&gt;The next improvements I would prioritise are:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Immutable image references:&lt;/strong&gt; Use unique release tags or image digests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automated testing:&lt;/strong&gt; Ensure the pipeline stops when tests fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull-request validation:&lt;/strong&gt; Run build and manifest checks before changes reach the deployment branch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controlled promotion:&lt;/strong&gt; Promote a tested image through staging and production with appropriate approvals.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supply-chain security:&lt;/strong&gt; Scan dependencies and container images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability:&lt;/strong&gt; Add application metrics, dashboards, logs, and actionable alerts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production networking:&lt;/strong&gt; Use a suitable ingress or gateway and secure TLS configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High availability:&lt;/strong&gt; Design for node failures and relevant failure domains.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data protection:&lt;/strong&gt; Implement and test database backup and restore procedures where persistent data is involved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery testing:&lt;/strong&gt; Practise rolling back to a known-good configuration and image.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Least privilege:&lt;/strong&gt; Reduce the permissions available to Jenkins, ArgoCD, and application workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment isolation:&lt;/strong&gt; Separate staging and production access and configuration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A platform becomes more mature when it can not only deploy software but also explain its state, recover from expected failures, and make unsafe changes more difficult.&lt;/p&gt;

&lt;h2&gt;
  
  
  23. Final Results and Validation Checklist
&lt;/h2&gt;

&lt;p&gt;The following table separates the demonstrated project outcomes from optional extensions.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;Result or validation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jenkins&lt;/td&gt;
&lt;td&gt;A successful pipeline build was achieved by the 20th CI/CD pipeline build&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Credential troubleshooting&lt;/td&gt;
&lt;td&gt;Identified the &lt;code&gt;github-credentials&lt;/code&gt; lookup error as a credential reference issue&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source control&lt;/td&gt;
&lt;td&gt;Application source stored in its GitHub repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitOps configuration&lt;/td&gt;
&lt;td&gt;Deployment configuration stored in a separate repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container image&lt;/td&gt;
&lt;td&gt;Docker image publication was part of the workflow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;Spring Boot application managed through a Deployment and Service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replicas&lt;/td&gt;
&lt;td&gt;GitOps configuration declared three replicas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ArgoCD&lt;/td&gt;
&lt;td&gt;Application managed through the GitOps repository&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-healing&lt;/td&gt;
&lt;td&gt;A manual scale-down was corrected back to the Git-defined replica count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application testing&lt;/td&gt;
&lt;td&gt;The tested endpoint returned HTTP 200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Persistent storage&lt;/td&gt;
&lt;td&gt;Documented as a reusable extension; verify separately if it was implemented&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend and backend&lt;/td&gt;
&lt;td&gt;Documented as a reference extension, not part of the confirmed Spring Boot-only deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production monitoring and HA&lt;/td&gt;
&lt;td&gt;Recommended future improvements, not assumed to be implemented&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Final validation commands
&lt;/h3&gt;

&lt;p&gt;Run these commands against the relevant environment:&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;# Kubernetes cluster&lt;/span&gt;
kubectl get nodes &lt;span class="nt"&gt;-o&lt;/span&gt; wide

&lt;span class="c"&gt;# Deployment and Pods&lt;/span&gt;
kubectl get deployment spring-boot-app
kubectl get pods &lt;span class="nt"&gt;-o&lt;/span&gt; wide

&lt;span class="c"&gt;# Service and endpoints&lt;/span&gt;
kubectl get service spring-boot-app-service
kubectl get endpoints spring-boot-app-service

&lt;span class="c"&gt;# ArgoCD application&lt;/span&gt;
kubectl get application spring-boot-app &lt;span class="nt"&gt;-n&lt;/span&gt; argocd

&lt;span class="c"&gt;# Render the GitOps manifests&lt;/span&gt;
kubectl kustomize apps/spring-boot-app/base

&lt;span class="c"&gt;# Review Git changes from the GitOps repository&lt;/span&gt;
git status
git log &lt;span class="nt"&gt;-5&lt;/span&gt; &lt;span class="nt"&gt;--oneline&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last two Git commands should be run from the GitOps repository. The Kubernetes commands should be run against the cluster and namespace where the application is deployed.&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%2Fs36z0umrnxzm04aj2dev.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%2Fs36z0umrnxzm04aj2dev.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyj9fpoubxj550kl3xftt.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%2Fyj9fpoubxj550kl3xftt.png" alt=" " width="800" height="416"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo7dusce5c9furgaahgo8.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%2Fo7dusce5c9furgaahgo8.png" alt=" " width="800" height="408"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fo9hmeala7xu99izuvfrb.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%2Fo9hmeala7xu99izuvfrb.png" alt=" " width="800" height="511"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - FINAL KUBERNETES STATUS HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flwi0vw5fn89rbn224zgg.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%2Flwi0vw5fn89rbn224zgg.png" alt=" " width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - FINAL ARGOCD SYNCHRONISATION STATUS HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftqdowxrt36bc2xxamqu5.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%2Ftqdowxrt36bc2xxamqu5.png" alt=" " width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[SCREENSHOT - THE FINAL SUCCESSFUL APPLICATION TEST HERE]&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  24. What I Learned From This Capstone
&lt;/h2&gt;

&lt;p&gt;This project strengthened my understanding of how application delivery works across several connected systems.&lt;/p&gt;

&lt;p&gt;I learned that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A pipeline is a sequence of dependent operations, and each stage must be debugged at the correct layer.&lt;/li&gt;
&lt;li&gt;Credential IDs, credential types, authentication, and permissions are separate concerns.&lt;/li&gt;
&lt;li&gt;Docker image publication does not prove that an application has deployed successfully.&lt;/li&gt;
&lt;li&gt;Kubernetes Deployments manage desired replica counts, while Services provide stable access to selected Pods.&lt;/li&gt;
&lt;li&gt;Persistent storage requires deliberate planning around claims, volumes, lifecycle, and recovery.&lt;/li&gt;
&lt;li&gt;GitOps makes the desired state visible and version-controlled.&lt;/li&gt;
&lt;li&gt;ArgoCD self-healing can correct configuration drift when the desired state and synchronisation policy support it.&lt;/li&gt;
&lt;li&gt;Application health must be verified independently of build and deployment status.&lt;/li&gt;
&lt;li&gt;Three replicas on a local Minikube cluster do not constitute production high availability.&lt;/li&gt;
&lt;li&gt;Security, observability, backups, and rollback procedures are part of operating a platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most memorable part was the troubleshooting. The Jenkins credential error was frustrating, but it taught me to stop guessing and investigate the exact failure.&lt;/p&gt;

&lt;p&gt;When an earlier pipeline stage succeeds and a later stage fails, the answer is not necessarily to rebuild everything. The better approach is to identify the failing operation, inspect its dependencies, and verify the relevant configuration.&lt;/p&gt;

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

&lt;p&gt;This capstone gave me practical experience connecting Jenkins, GitHub, Maven, Docker, Kubernetes, and ArgoCD into a repeatable delivery workflow.&lt;/p&gt;

&lt;p&gt;It also helped me understand why CI/CD and GitOps are complementary. CI/CD automates the process of validating and packaging application changes. GitOps provides a declarative way to manage the desired deployment state and reconcile the running environment with it.&lt;/p&gt;

&lt;p&gt;The project is a foundation I can continue improving with stronger automated testing, a more complete frontend/backend architecture, persistent data services where needed, better monitoring, and more production-oriented security and availability controls.&lt;/p&gt;

&lt;p&gt;My biggest takeaway is that the tools are only part of the work. Understanding the connections between them, investigating failures systematically, and being able to explain the result are just as important.&lt;/p&gt;

&lt;p&gt;For anyone learning DevOps or platform engineering, I would encourage you to document not only what worked but also the errors you encountered, how you investigated them, and what the final tests proved.&lt;/p&gt;

&lt;p&gt;That is what turns a practical exercise into knowledge you can reuse in your next project.&lt;/p&gt;




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

&lt;p&gt;&lt;strong&gt;GitHub profile:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Jahmeeu-Cloud" rel="noopener noreferrer"&gt;https://github.com/Jahmeeu-Cloud&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application source repository:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Jahmeeu-Cloud/platform-cicd-spring-boot-demo-source" rel="noopener noreferrer"&gt;https://github.com/Jahmeeu-Cloud/platform-cicd-spring-boot-demo-source&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitOps configuration repository:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://github.com/Jahmeeu-Cloud/platform-cicd-gitops-config" rel="noopener noreferrer"&gt;https://github.com/Jahmeeu-Cloud/platform-cicd-gitops-config&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; Jamiu Olatunji Bakre &lt;br&gt;
&lt;em&gt;Cloud, DevOps and Platform Engineer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This article documents a hands-on learning project and includes additional reference configurations for future extensions. Optional components are identified separately from the functionality verified in the original environment.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>jenkins</category>
      <category>docker</category>
      <category>kubernetes</category>
      <category>argocd</category>
    </item>
    <item>
      <title>Capstone Project: Building an End-to-End AWS Kubernetes Platform with Terraform, Docker, Amazon EKS, and PostgreSQL</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Sun, 18 Oct 2026 16:25:51 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/building-an-end-to-end-aws-kubernetes-platform-with-terraform-docker-amazon-eks-and-postgresql-52kk</link>
      <guid>https://dev.to/jamiu_cloud/building-an-end-to-end-aws-kubernetes-platform-with-terraform-docker-amazon-eks-and-postgresql-52kk</guid>
      <description>&lt;p&gt;&lt;strong&gt;A practical Platform Engineering capstone: from infrastructure provisioning to application deployment, persistent storage, autoscaling, validation, and cleanup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Author:&lt;/strong&gt; Jamiu Bakre&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Project:&lt;/strong&gt; AWS Kubernetes Platform Engineering Capstone&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Cloud Provider:&lt;/strong&gt; Amazon Web Services (AWS)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Infrastructure as Code:&lt;/strong&gt; Terraform&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Containerization:&lt;/strong&gt; Docker&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Container Orchestration:&lt;/strong&gt; Kubernetes on Amazon EKS&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Container Registry:&lt;/strong&gt; Amazon Elastic Container Registry (ECR)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Application Load Balancing:&lt;/strong&gt; AWS Application Load Balancer (ALB)&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Database:&lt;/strong&gt; PostgreSQL&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Primary AWS Region:&lt;/strong&gt; &lt;code&gt;eu-west-1&lt;/code&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Kubernetes Cluster:&lt;/strong&gt; &lt;code&gt;platform-capstone-prod&lt;/code&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;One of the things I wanted to achieve with this capstone was to go beyond deploying an application to a cloud server. I wanted to build a platform that demonstrated how cloud infrastructure, containers, Kubernetes, persistent storage, networking, security, monitoring, and automation work together in a production-oriented environment.&lt;/p&gt;

&lt;p&gt;The project started with a familiar application architecture: a frontend, a backend API, and a PostgreSQL database. Rather than deploying these components manually to individual servers, I used Terraform to define the cloud infrastructure and Kubernetes to manage the application workloads.&lt;/p&gt;

&lt;p&gt;The goal was to create an environment that could be provisioned consistently, support application deployment, recover from ordinary workload failures, scale when demand increased, and provide a clear path for future improvements.&lt;/p&gt;

&lt;p&gt;This was also an opportunity to bring together several areas of cloud and platform engineering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Infrastructure as Code with Terraform.&lt;/li&gt;
&lt;li&gt;AWS networking and private application infrastructure.&lt;/li&gt;
&lt;li&gt;Container image creation with Docker.&lt;/li&gt;
&lt;li&gt;Image storage and distribution through Amazon ECR.&lt;/li&gt;
&lt;li&gt;Kubernetes workload orchestration with Amazon EKS.&lt;/li&gt;
&lt;li&gt;Persistent application data using PostgreSQL, PersistentVolumes, and PersistentVolumeClaims.&lt;/li&gt;
&lt;li&gt;External application access through an AWS Application Load Balancer.&lt;/li&gt;
&lt;li&gt;Health monitoring, resource requests and limits, and Horizontal Pod Autoscaling.&lt;/li&gt;
&lt;li&gt;Infrastructure validation, application testing, documentation, and cleanup.&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftb6ufnvunm62nsuz29ci.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%2Ftb6ufnvunm62nsuz29ci.png" alt=" " width="799" height="254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Frontend&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkbbrtvfvxztduc393hx3.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%2Fkbbrtvfvxztduc393hx3.png" alt=" " width="800" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frohwsi6py02fvjxyr76e.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%2Frohwsi6py02fvjxyr76e.png" alt=" " width="799" height="238"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Backend health Status&lt;/strong&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq3xk0jh49orb3gvxbevy.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%2Fq3xk0jh49orb3gvxbevy.png" alt=" " width="800" height="403"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live status&lt;/p&gt;

&lt;p&gt;The important part of this project was not simply getting the application to run. It was understanding how each component depends on the others and how to validate the system as a whole.&lt;/p&gt;
&lt;h3&gt;
  
  
  Project objectives
&lt;/h3&gt;

&lt;p&gt;The main objectives were to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provision the AWS infrastructure using Terraform.&lt;/li&gt;
&lt;li&gt;Create a VPC with public and private subnets.&lt;/li&gt;
&lt;li&gt;Deploy an Amazon EKS cluster with managed worker nodes.&lt;/li&gt;
&lt;li&gt;Build and publish the frontend and backend container images to Amazon ECR.&lt;/li&gt;
&lt;li&gt;Deploy the frontend, backend API, and PostgreSQL database to Kubernetes.&lt;/li&gt;
&lt;li&gt;Expose the frontend through an AWS Application Load Balancer.&lt;/li&gt;
&lt;li&gt;Configure persistent database storage.&lt;/li&gt;
&lt;li&gt;Implement application health probes and resource management.&lt;/li&gt;
&lt;li&gt;Configure and validate Horizontal Pod Autoscaling.&lt;/li&gt;
&lt;li&gt;Test database recovery after a PostgreSQL Pod restart.&lt;/li&gt;
&lt;li&gt;Validate the Terraform configuration and maintain the project in GitHub.&lt;/li&gt;
&lt;li&gt;Clean up the deployed infrastructure while preserving the Terraform state in Amazon S3.&lt;/li&gt;
&lt;/ol&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%2F5o3uq88ku2zxxlgfuobu.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%2F5o3uq88ku2zxxlgfuobu.png" alt=" " width="586" height="765"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: Capstone assignment and objectives.]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1whx9hkgs177z8hrwmxv.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%2F1whx9hkgs177z8hrwmxv.png" alt=" " width="800" height="387"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The GitHub repository homepage showing the project files.]&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  2. Understanding the architecture
&lt;/h2&gt;

&lt;p&gt;Before writing Terraform or Kubernetes manifests, it is useful to understand how the application and infrastructure fit together.&lt;/p&gt;

&lt;p&gt;The platform has three application components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; serves the user-facing web application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; provides application logic and API endpoints.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL:&lt;/strong&gt; stores application data and provides persistent database storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The frontend and backend run as Kubernetes Deployments. PostgreSQL runs as a StatefulSet with persistent storage. Kubernetes Services provide stable internal networking, while an Ingress resource instructs the AWS Load Balancer Controller to create an Application Load Balancer for external access.&lt;/p&gt;
&lt;h3&gt;
  
  
  High-level architecture
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Internet
                            |
                            v
                 AWS Application Load Balancer
                            |
                            v
                  Kubernetes Ingress
                            |
                            v
                    Frontend Service
                            |
                            v
                    Frontend Pods
                            |
                       API requests
                            |
                            v
                    Backend Service
                            |
                            v
                    Backend Pods
                            |
                            v
                    PostgreSQL Service
                            |
                            v
                   PostgreSQL StatefulSet
                            |
                            v
                  PersistentVolumeClaim
                            |
                            v
                 Encrypted EBS gp3 Volume


       AWS Infrastructure managed with Terraform
       -------------------------------------------
       VPC
       ├── Public subnets
       ├── Private subnets
       ├── Internet Gateway
       ├── NAT Gateways
       ├── Route tables
       ├── Security groups
       ├── Amazon EKS cluster
       ├── Managed worker node group
       └── Amazon ECR repositories

       Container delivery
       ------------------
       Application source code
                |
                v
             Docker
                |
                v
          Amazon ECR
                |
                v
          Amazon EKS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Important networking distinction:&lt;/strong&gt; the deployed Ingress configuration routed public traffic to the frontend on HTTP port 80. The backend and PostgreSQL were intended to remain internal services. The diagram illustrates the application's logical communication path; actual browser-to-backend communication depends on the frontend configuration, such as an Nginx API proxy or additional Ingress rules.&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%2Fu0x1jbs5qtifozut2nzs.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%2Fu0x1jbs5qtifozut2nzs.png" alt=" " width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: Architecture diagram.]&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  How the components work together
&lt;/h3&gt;

&lt;p&gt;When a user visits the application, the request reaches the Application Load Balancer. The ALB routes traffic according to the Kubernetes Ingress configuration. In this implementation, the root path &lt;code&gt;/&lt;/code&gt; routes to the frontend service.&lt;/p&gt;

&lt;p&gt;The frontend runs in one or more Pods. When it needs backend functionality, it communicates with the backend through the configured API endpoint. The backend communicates with PostgreSQL through an internal Kubernetes Service.&lt;/p&gt;

&lt;p&gt;Kubernetes continuously monitors the declared workloads. If a frontend or backend Pod fails, the corresponding Deployment can create a replacement. PostgreSQL uses a StatefulSet and a PersistentVolumeClaim so that database storage can outlive an individual Pod.&lt;/p&gt;

&lt;p&gt;Terraform manages the AWS infrastructure, while Kubernetes manages the application workloads. Keeping these responsibilities separate makes the platform easier to maintain.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Technology stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technology&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 VPC&lt;/td&gt;
&lt;td&gt;Isolated cloud networking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public and private subnets&lt;/td&gt;
&lt;td&gt;Network separation and workload placement&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internet Gateway&lt;/td&gt;
&lt;td&gt;Internet connectivity for public resources&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateways&lt;/td&gt;
&lt;td&gt;Outbound connectivity from private subnets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Terraform&lt;/td&gt;
&lt;td&gt;Reproducible infrastructure provisioning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon EKS&lt;/td&gt;
&lt;td&gt;Managed Kubernetes control plane&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EKS managed node group&lt;/td&gt;
&lt;td&gt;Worker compute capacity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon ECR&lt;/td&gt;
&lt;td&gt;Container image registry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker&lt;/td&gt;
&lt;td&gt;Application containerization&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes Deployment&lt;/td&gt;
&lt;td&gt;Frontend and backend workload management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes Service&lt;/td&gt;
&lt;td&gt;Stable internal networking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;StatefulSet&lt;/td&gt;
&lt;td&gt;Stable identity and storage association for PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PersistentVolumeClaim&lt;/td&gt;
&lt;td&gt;Request for persistent storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon EBS CSI driver&lt;/td&gt;
&lt;td&gt;Provisioning and attachment of EBS storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS Load Balancer Controller&lt;/td&gt;
&lt;td&gt;AWS load balancer integration with Kubernetes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application Load Balancer&lt;/td&gt;
&lt;td&gt;Public HTTP entry point&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metrics Server&lt;/td&gt;
&lt;td&gt;Kubernetes resource usage metrics&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Horizontal Pod Autoscaler&lt;/td&gt;
&lt;td&gt;Automatic replica scaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;td&gt;Remote Terraform state storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git and GitHub&lt;/td&gt;
&lt;td&gt;Version control and project delivery&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl6smyun1kd0lbf312has.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%2Fl6smyun1kd0lbf312has.png" alt=" " width="799" height="418"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdce6tfwbflg72c2k8qjb.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%2Fdce6tfwbflg72c2k8qjb.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ft6zkqitpu5rks3afwr94.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%2Ft6zkqitpu5rks3afwr94.png" alt=" " width="799" height="190"&gt;&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fl1cydow67p53epgt3c0o.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%2Fl1cydow67p53epgt3c0o.png" alt=" " width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: Terraform, Docker, Kubernetes and  AWS tools used in the project.]&lt;/strong&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  Phase 1: Preparing the development environment
&lt;/h1&gt;
&lt;h2&gt;
  
  
  4. Tools and prerequisites
&lt;/h2&gt;

&lt;p&gt;Before provisioning infrastructure, the local environment needs the tools required to create cloud resources, build containers, and interact with Kubernetes.&lt;/p&gt;

&lt;p&gt;The main tools used in this project were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS CLI.&lt;/li&gt;
&lt;li&gt;Terraform.&lt;/li&gt;
&lt;li&gt;Docker.&lt;/li&gt;
&lt;li&gt;kubectl.&lt;/li&gt;
&lt;li&gt;Git.&lt;/li&gt;
&lt;li&gt;Helm.&lt;/li&gt;
&lt;li&gt;Graphviz, for generating a Terraform dependency graph.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The development environment was Ubuntu under Windows Subsystem for Linux (WSL).&lt;/p&gt;
&lt;h3&gt;
  
  
  4.1 Verify the installed tools
&lt;/h3&gt;

&lt;p&gt;Run the following commands in your terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws &lt;span class="nt"&gt;--version&lt;/span&gt;
terraform version
docker &lt;span class="nt"&gt;--version&lt;/span&gt;
kubectl version &lt;span class="nt"&gt;--client&lt;/span&gt;
helm version
git &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you intend to generate an infrastructure dependency graph, verify Graphviz as well:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dot &lt;span class="nt"&gt;-V&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If Graphviz is not installed in Ubuntu:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;graphviz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  4.2 Configure AWS access
&lt;/h3&gt;

&lt;p&gt;Configure the AWS CLI using your approved AWS credentials:&lt;br&gt;
&lt;/p&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;The CLI prompts for an access key, secret key, default region, and output format. Use an IAM identity with the permissions needed for the project. Never commit AWS credentials to Git.&lt;/p&gt;

&lt;p&gt;Verify which account is active:&lt;br&gt;
&lt;/p&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;p&gt;The output identifies the AWS account and the IAM identity used for subsequent operations.&lt;/p&gt;

&lt;p&gt;Confirm the intended region:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;For this project, the primary region was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;eu-west-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If necessary, set a shell variable for the current session:&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;export &lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"eu-west-1"&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;"&lt;/span&gt;&lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt; AWS commands can create or destroy real resources. Confirming the account and region before running Terraform prevents accidental changes to another environment.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 2: Organizing the project
&lt;/h1&gt;

&lt;h2&gt;
  
  
  5. Repository structure
&lt;/h2&gt;

&lt;p&gt;A well-organized repository separates infrastructure configuration, reusable Terraform modules, Kubernetes manifests, and application source code.&lt;/p&gt;

&lt;p&gt;The following is a representative structure for this project. Adjust the file names to match the actual repository before publishing it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-capstone/
├── terraform/
│   ├── bootstrap/
│   │   └── main.tf
│   ├── environments/
│   │   └── prod/
│   │       ├── main.tf
│   │       ├── variables.tf
│   │       ├── outputs.tf
│   │       ├── providers.tf
│   │       ├── versions.tf
│   │       └── backend.tf
│   └── modules/
│       ├── vpc/
│       ├── ecr/
│       └── eks/
├── app/
│   ├── frontend/
│   │   ├── Dockerfile
│   │   └── nginx.conf
│   └── backend/
│       ├── Dockerfile
│       ├── package.json
│       └── server.js
├── k8s/
│   ├── namespace.yaml
│   ├── postgres-secret.example.yaml
│   ├── postgres-service.yaml
│   ├── postgres-statefulset.yaml
│   ├── frontend-deployment.yaml
│   ├── frontend-service.yaml
│   ├── backend-deployment.yaml
│   ├── backend-service.yaml
│   ├── hpa.yaml
│   └── ingress.yaml
├── docs/
│   └── screenshots/
├── .gitignore
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exact repository may use a different layout. The key principle is to keep reusable infrastructure modules separate from the environment-specific configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  5.1 Why separate Terraform modules?
&lt;/h3&gt;

&lt;p&gt;Modules allow infrastructure components to be defined once and reused.&lt;/p&gt;

&lt;p&gt;For example, the VPC module can own the VPC, subnets, routes, and networking resources. The ECR module can create repositories. The EKS module can manage the Kubernetes cluster and worker nodes.&lt;/p&gt;

&lt;p&gt;The production environment then connects these modules by passing outputs from one module into another.&lt;/p&gt;

&lt;p&gt;This reduces duplication and makes the infrastructure easier to review and extend.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 3: Provisioning infrastructure with Terraform
&lt;/h1&gt;

&lt;h2&gt;
  
  
  6. Terraform design
&lt;/h2&gt;

&lt;p&gt;Terraform was used to define the AWS resources declaratively. Instead of creating resources individually through the AWS Console, the configuration describes the desired infrastructure and allows Terraform to determine the required changes.&lt;/p&gt;

&lt;p&gt;The core infrastructure consisted of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A VPC.&lt;/li&gt;
&lt;li&gt;Public and private subnets across two Availability Zones.&lt;/li&gt;
&lt;li&gt;Internet and NAT connectivity.&lt;/li&gt;
&lt;li&gt;Route tables and security controls.&lt;/li&gt;
&lt;li&gt;Amazon EKS.&lt;/li&gt;
&lt;li&gt;Managed worker nodes.&lt;/li&gt;
&lt;li&gt;Amazon ECR repositories.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The deployed worker nodes were two &lt;code&gt;t3.medium&lt;/code&gt; instances. The EKS cluster was named &lt;code&gt;platform-capstone-prod&lt;/code&gt; and used Kubernetes version &lt;code&gt;1.35&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.1 Configure the Terraform provider
&lt;/h3&gt;

&lt;p&gt;A representative provider configuration is:&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.5.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; 6.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;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="nx"&gt;default_tags&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;Project&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"platform-capstone"&lt;/span&gt;
      &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"prod"&lt;/span&gt;
      &lt;span class="nx"&gt;ManagedBy&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&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;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 for the platform"&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;"eu-west-1"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The AWS provider version constraint should match the version selected and tested in the repository's lock file. Do not upgrade a working provider configuration without reviewing the plan.&lt;/p&gt;

&lt;p&gt;Default tags make resources easier to identify for operations, auditing, and cost analysis.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.2 Define the infrastructure modules
&lt;/h3&gt;

&lt;p&gt;A simplified root configuration could look 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;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;"../../modules/vpc"&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;"platform-capstone-prod"&lt;/span&gt;
  &lt;span class="nx"&gt;vpc_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;availability_zones&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"eu-west-1a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1b"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="nx"&gt;public_subnet_cidrs&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_subnet_cidrs&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.11.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"10.0.12.0/24"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"ecr"&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;"../../modules/ecr"&lt;/span&gt;

  &lt;span class="nx"&gt;repositories&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"platform-capstone-prod/frontend"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="s2"&gt;"platform-capstone-prod/backend"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&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;"../../modules/eks"&lt;/span&gt;

  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"platform-capstone-prod"&lt;/span&gt;
  &lt;span class="nx"&gt;kubernetes_version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.35"&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;private_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_subnet_ids&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is an illustrative module interface, not a complete drop-in configuration. The variables and outputs must correspond to those defined in the actual modules.&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%2F6txf72k1x5vetutsbrue.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%2F6txf72k1x5vetutsbrue.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The EKS module also needs to define the cluster IAM permissions, managed node group, networking configuration, and any required access entries or authentication configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.3 Build the VPC
&lt;/h3&gt;

&lt;p&gt;The VPC provides the network boundary for the platform.&lt;/p&gt;

&lt;p&gt;The project used two public subnets and two private subnets across two Availability Zones.&lt;/p&gt;

&lt;p&gt;Public subnets support resources that require a route to the Internet Gateway, such as an internet-facing ALB. Private subnets are appropriate for worker nodes and other internal workloads.&lt;/p&gt;

&lt;p&gt;The public route table typically includes a default route to the Internet Gateway:&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_route"&lt;/span&gt; &lt;span class="s2"&gt;"public_internet"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;route_table_id&lt;/span&gt;         &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_route_table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;public&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;destination_cidr_block&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="nx"&gt;gateway_id&lt;/span&gt;             &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_internet_gateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;this&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Private subnets can use NAT Gateways for outbound connectivity where required. A NAT Gateway is not a substitute for security groups or application authentication; it provides network address translation for outbound connections.&lt;/p&gt;

&lt;p&gt;This design improves network separation, but it has a cost implication. NAT Gateways incur hourly and data-processing charges. The capstone used two NAT Gateways, one for each Availability Zone.&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%2Fsehn9u5u8a7erwygfjy4.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%2Fsehn9u5u8a7erwygfjy4.png" alt=" " width="800" height="414"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The Terraform plan showing the VPC, subnet, route, and gateway resources.]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw4w1imcqseb62v9en64i.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%2Fw4w1imcqseb62v9en64i.png" alt=" " width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The AWS VPC console showing the public and private subnets across two Availability Zones.]&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6.4 Create Amazon ECR repositories
&lt;/h3&gt;

&lt;p&gt;Amazon ECR stores the Docker images that Kubernetes pulls when starting application Pods.&lt;/p&gt;

&lt;p&gt;The two repositories were:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-capstone-prod/frontend
platform-capstone-prod/backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A Terraform module can create them using &lt;code&gt;aws_ecr_repository&lt;/code&gt;. The following is a representative resource:&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_ecr_repository"&lt;/span&gt; &lt;span class="s2"&gt;"frontend"&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;"platform-capstone-prod/frontend"&lt;/span&gt;
  &lt;span class="nx"&gt;image_tag_mutability&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"IMMUTABLE"&lt;/span&gt;

  &lt;span class="nx"&gt;image_scanning_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;scan_on_push&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="nx"&gt;encryption_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;encryption_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AES256"&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;Project&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"platform-capstone"&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;Repeat the configuration for the backend or use a reusable module.&lt;/p&gt;

&lt;p&gt;Immutable tags help prevent a tag from being silently reassigned to another image. If you use immutable tags, publish new version tags for each build instead of repeatedly pushing to the same tag.&lt;/p&gt;

&lt;h3&gt;
  
  
  6.5 Create the EKS cluster and worker nodes
&lt;/h3&gt;

&lt;p&gt;Amazon EKS manages the Kubernetes control plane. The worker nodes provide compute capacity for application Pods.&lt;/p&gt;

&lt;p&gt;The platform used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cluster name: &lt;code&gt;platform-capstone-prod&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Kubernetes version: &lt;code&gt;1.35&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Managed worker nodes: two &lt;code&gt;t3.medium&lt;/code&gt; instances.&lt;/li&gt;
&lt;li&gt;Worker placement: private subnets.&lt;/li&gt;
&lt;li&gt;VPC networking: the VPC created by Terraform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The EKS module must define a cluster IAM role, node IAM role, networking, cluster access, and the managed node group. It should also establish appropriate security group rules.&lt;/p&gt;

&lt;p&gt;The control plane and worker nodes have different responsibilities. The control plane manages the Kubernetes API, scheduling, and cluster state. The worker nodes run the actual application containers.&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%2Fkqch07augzkxeh5x33oo.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%2Fkqch07augzkxeh5x33oo.png" alt=" " width="800" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The EKS console showing &lt;code&gt;platform-capstone-prod&lt;/code&gt; and the active node group.]&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  6.6 Initialize, validate, plan, and apply
&lt;/h3&gt;

&lt;p&gt;Run Terraform commands from the correct root directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/platform-capstone/terraform/environments/prod

terraform &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;-recursive&lt;/span&gt;
terraform init
terraform validate
terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the plan carefully before applying it:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Terraform displays the proposed actions and requests confirmation. Review resource creation, replacement, and destruction before approving.&lt;/p&gt;

&lt;p&gt;After deployment, verify the state:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The exact outputs depend on the repository configuration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why this sequence matters:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;terraform fmt&lt;/code&gt; standardizes formatting.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform init&lt;/code&gt; initializes providers, modules, and the backend.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform validate&lt;/code&gt; checks the configuration for structural errors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform plan&lt;/code&gt; previews the intended changes.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform apply&lt;/code&gt; executes the approved plan.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;terraform state list&lt;/code&gt; helps verify which resources Terraform is tracking.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A successful &lt;code&gt;terraform validate&lt;/code&gt; does not prove that AWS resources can be created or that the application works. Those require deployment and runtime testing.&lt;/p&gt;




&lt;h2&gt;
  
  
  7. Remote Terraform state with Amazon S3
&lt;/h2&gt;

&lt;p&gt;Terraform state records the mapping between Terraform resource definitions and the infrastructure Terraform manages.&lt;/p&gt;

&lt;p&gt;For this project, the remote state bucket was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-capstone-terraform-state-911xxxx03075
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The state key was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-capstone/prod/terraform.tfstate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend was configured for &lt;code&gt;eu-west-1&lt;/code&gt;, with S3 versioning and public access blocking. The configuration used server-side encryption and S3-native state locking through &lt;code&gt;use_lockfile&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A representative backend block is:&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;"platform-capstone-terraform-state-9111xxxx3075"&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;"platform-capstone/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;"eu-west-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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bucket must exist before Terraform can initialize this backend. For a fresh deployment, the bootstrap process should create and secure the bucket first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why remote state matters
&lt;/h3&gt;

&lt;p&gt;Remote state provides a central location for Terraform's resource mapping. It is particularly useful when working across different sessions or collaborating with other engineers.&lt;/p&gt;

&lt;p&gt;State files may contain sensitive values. Restrict access through IAM, prevent public access, enable versioning, and never commit state files to a public Git repository.&lt;/p&gt;

&lt;p&gt;After the capstone teardown, the state file remained in S3. This preserves the deployment record, but it does not mean the AWS infrastructure is still running.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 4: Building and publishing container images
&lt;/h1&gt;

&lt;h2&gt;
  
  
  8. Why Docker was used
&lt;/h2&gt;

&lt;p&gt;Docker packages an application and its runtime dependencies into a container image. The image can be built once, stored in a registry, and deployed consistently across environments.&lt;/p&gt;

&lt;p&gt;The platform used separate images for the frontend and backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;frontend:v2
backend:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The images were stored in Amazon ECR.&lt;/p&gt;

&lt;p&gt;Using separate images also allows each component to be deployed and updated independently.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Building the backend API image
&lt;/h2&gt;

&lt;p&gt;The backend used Node.js and Express and listened on port &lt;code&gt;5000&lt;/code&gt;. It exposed liveness and readiness endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/health/live
/health/ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The liveness endpoint indicates whether the process is alive. The readiness endpoint checks whether the application is ready to receive traffic and, in this implementation, includes a database connectivity check.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.1 Representative backend application
&lt;/h3&gt;

&lt;p&gt;The following is a simplified example of the pattern. Adapt it to the actual backend source and database library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&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;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;express&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/health/live&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;alive&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/health/ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Replace this with a real PostgreSQL connectivity check.&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;not-ready&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;_req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Backend API is running&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;port&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0.0.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Backend listening on port &lt;/span&gt;&lt;span class="p"&gt;${&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;`&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;Important:&lt;/strong&gt; the example readiness handler does not actually connect to PostgreSQL. In the deployed application, readiness must query the database or use the real connection pool to verify connectivity. A successful HTTP response alone is not proof that the database is healthy.&lt;/p&gt;

&lt;h3&gt;
  
  
  9.2 Representative backend Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:22-alpine&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci &lt;span class="nt"&gt;--omit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;dev

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;

&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; NODE_ENV=production&lt;/span&gt;
&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; PORT=5000&lt;/span&gt;

&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="s"&gt; node&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 5000&lt;/span&gt;

&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["node", "server.js"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example uses a non-root runtime user. The Node.js version must match the application's supported runtime and the version actually tested in the repository.&lt;/p&gt;

&lt;p&gt;For a production image, also consider a &lt;code&gt;.dockerignore&lt;/code&gt; file, dependency pinning, vulnerability scanning, and reducing unnecessary files in the image.&lt;/p&gt;

&lt;p&gt;Example &lt;code&gt;.dockerignore&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;node_modules
.git
.env
npm-debug.log
coverage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Caveat: Do not copy production credentials into the image!&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  10. Building the frontend image
&lt;/h2&gt;

&lt;p&gt;The frontend was served using Nginx on port &lt;code&gt;8080&lt;/code&gt;, running as a non-root user.&lt;/p&gt;

&lt;p&gt;For a static frontend, a multi-stage Docker build can compile the application and then copy only the production assets into the Nginx image.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.1 Representative frontend Dockerfile
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;node:22-alpine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;build&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package*.json ./&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm ci

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm run build

&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; nginxinc/nginx-unprivileged:stable-alpine&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=build /app/dist/ /usr/share/nginx/html/&lt;/span&gt;

&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="s"&gt; 8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example assumes the frontend build output is &lt;code&gt;dist/&lt;/code&gt;. Change the source path if the application uses a different build directory.&lt;/p&gt;

&lt;p&gt;If the frontend needs a custom Nginx configuration, provide one compatible with the unprivileged Nginx image. Do not assume the default configuration listens on port &lt;code&gt;8080&lt;/code&gt;; verify it.&lt;/p&gt;

&lt;h3&gt;
  
  
  10.2 Build and test the images locally
&lt;/h3&gt;

&lt;p&gt;Example commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-t&lt;/span&gt; platform-frontend:v2 ./app/frontend
docker build &lt;span class="nt"&gt;-t&lt;/span&gt; platform-backend:v1 ./app/backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the image sizes:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The capstone assessment included a target of keeping both images below 150 MB. Check the actual image sizes rather than assuming the target was met.&lt;/p&gt;

&lt;p&gt;Run the containers locally before publishing:&lt;br&gt;
&lt;/p&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; 8080:8080 platform-frontend:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In another terminal, test the frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; http://localhost:8080/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the backend:&lt;br&gt;
&lt;/p&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 platform-backend:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test the health endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://localhost:5000/health/live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the application's actual database configuration when testing readiness. If the database is unavailable, a correctly implemented readiness check should not claim the application is ready.&lt;/p&gt;




&lt;h2&gt;
  
  
  11. Push images to Amazon ECR
&lt;/h2&gt;

&lt;p&gt;Before pushing, obtain the AWS account ID:&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;AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;aws sts get-caller-identity &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--query&lt;/span&gt; Account &lt;span class="nt"&gt;--output&lt;/span&gt; text&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Log Docker into ECR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr get-login-password &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; |
  docker login &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--username&lt;/span&gt; AWS &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--password-stdin&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.dkr.ecr.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Define the image URIs:&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;FRONTEND_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.dkr.ecr.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com/platform-capstone-prod/frontend"&lt;/span&gt;
&lt;span class="nv"&gt;BACKEND_URI&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.dkr.ecr.&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;AWS_REGION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.amazonaws.com/platform-capstone-prod/backend"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tag the images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker tag platform-frontend:v2 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FRONTEND_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:v2"&lt;/span&gt;
docker tag platform-backend:v1 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKEND_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:v1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Push them:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker push &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;FRONTEND_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:v2"&lt;/span&gt;
docker push &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BACKEND_URI&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:v1"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the repository images:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ecr describe-images &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; platform-capstone-prod/frontend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

aws ecr describe-images &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--repository-name&lt;/span&gt; platform-capstone-prod/backend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AWS_REGION&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The EKS worker nodes must have permission to pull these images. This normally involves the appropriate ECR permissions on the node IAM role or another configured image-pull identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why use ECR?&lt;/strong&gt; It provides a private registry integrated with AWS IAM. It separates the process of building an image from the process of deploying that image.&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%2Fvj95tcwcaza9babkjc8w.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%2Fvj95tcwcaza9babkjc8w.png" alt=" " width="799" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The ECR frontend and backend repository with its image tag.]&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 5: Configuring access to Amazon EKS
&lt;/h1&gt;

&lt;h2&gt;
  
  
  12. Connect kubectl to the cluster
&lt;/h2&gt;

&lt;p&gt;After Terraform creates the EKS cluster, configure &lt;code&gt;kubectl&lt;/code&gt; to use it:&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="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; platform-capstone-prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verify the active context:&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 current-context
kubectl cluster-info
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the worker nodes:&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;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The expected result was two worker nodes in the &lt;code&gt;Ready&lt;/code&gt; state.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;Ready&lt;/code&gt; status means Kubernetes considers the nodes healthy enough to participate in scheduling. It does not prove that every application or dependency is working.&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%2Fvoa2rw63dmoekyyaz2ft.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%2Fvoa2rw63dmoekyyaz2ft.png" alt=" " width="800" height="141"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: Insert &lt;code&gt;kubectl get nodes -o wide&lt;/code&gt; showing both Ready nodes.]&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  13. Verify cluster add-ons
&lt;/h2&gt;

&lt;p&gt;The cluster included the following add-ons and supporting components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;VPC CNI.&lt;/li&gt;
&lt;li&gt;CoreDNS.&lt;/li&gt;
&lt;li&gt;kube-proxy.&lt;/li&gt;
&lt;li&gt;Amazon EBS CSI driver.&lt;/li&gt;
&lt;li&gt;EKS Pod Identity Agent.&lt;/li&gt;
&lt;li&gt;Metrics Server.&lt;/li&gt;
&lt;li&gt;AWS Load Balancer Controller.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Verify EKS-managed add-ons:&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 list-addons &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; platform-capstone-prod &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check Kubernetes system 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;-n&lt;/span&gt; kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check Metrics 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 top nodes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If metrics are not yet available, investigate the Metrics Server deployment and its connectivity to the kubelets. Installing an HPA resource alone does not guarantee that resource metrics are available.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 6: Deploying the application to Kubernetes
&lt;/h1&gt;

&lt;h2&gt;
  
  
  14. Create a dedicated namespace
&lt;/h2&gt;

&lt;p&gt;A namespace groups the application's Kubernetes resources and makes them easier to inspect and remove.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;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;platform&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; namespace.yaml
&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 namespaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application resources were deployed into the &lt;code&gt;platform&lt;/code&gt; namespace.&lt;/p&gt;

&lt;h2&gt;
  
  
  15. Configure application secrets
&lt;/h2&gt;

&lt;p&gt;The backend and PostgreSQL need database connection settings. Credentials should not be placed directly in a Docker image or committed to a public repository.&lt;/p&gt;

&lt;p&gt;A Kubernetes Secret is one possible mechanism for supplying sensitive values. For a production system, consider a managed secret store such as AWS Secrets Manager and an appropriate integration.&lt;/p&gt;

&lt;p&gt;A sample Secret template is shown below for illustration only:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Secret&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres-secret&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;platform&lt;/span&gt;
&lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Opaque&lt;/span&gt;
&lt;span class="na"&gt;stringData&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appdb&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;appuser&lt;/span&gt;
  &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;REPLACE_WITH_A_SECRET&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not apply this example with a real production password stored in source control. Populate sensitive values through a secure process, and ensure the Secret is not committed to Git.&lt;/p&gt;

&lt;p&gt;A &lt;code&gt;.gitignore&lt;/code&gt; can exclude local secret files:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env
.env.*
!*.env.example
*secret*.yaml
terraform.tfstate
terraform.tfstate.*
.terraform/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Review the ignore rules carefully. The last rule can exclude filenames containing &lt;code&gt;secret&lt;/code&gt;; adapt it if your repository intentionally tracks safe example templates. &lt;code&gt;.gitignore&lt;/code&gt; does not remove files already committed to Git.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 7: Deploying PostgreSQL with persistent storage
&lt;/h1&gt;

&lt;h2&gt;
  
  
  16. Why PostgreSQL uses a StatefulSet
&lt;/h2&gt;

&lt;p&gt;A database has different operational needs from a stateless frontend.&lt;/p&gt;

&lt;p&gt;A frontend Pod can generally be replaced without preserving local filesystem state. A database must preserve its data when a Pod is recreated or moved.&lt;/p&gt;

&lt;p&gt;The capstone used a PostgreSQL StatefulSet with a 20 GiB encrypted gp3 EBS-backed PersistentVolumeClaim.&lt;/p&gt;

&lt;p&gt;The important Kubernetes storage concepts are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PersistentVolume (PV):&lt;/strong&gt; represents storage available to a workload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PersistentVolumeClaim (PVC):&lt;/strong&gt; requests storage with specified capacity and access requirements.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StorageClass:&lt;/strong&gt; defines how storage is dynamically provisioned.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StatefulSet:&lt;/strong&gt; manages Pods with stable identities and storage associations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EBS CSI driver:&lt;/strong&gt; allows Kubernetes to provision and manage Amazon EBS volumes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A PVC is a request for storage, not the data itself. When dynamically provisioned, Kubernetes and the CSI driver arrange for an appropriate volume to be created and bound to the claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  17. Understand the storage lifecycle
&lt;/h2&gt;

&lt;p&gt;The storage flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PostgreSQL StatefulSet
          |
          v
 volumeClaimTemplates
          |
          v
 PersistentVolumeClaim
          |
          v
 StorageClass (gp3)
          |
          v
 Amazon EBS CSI Driver
          |
          v
 Encrypted EBS Volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;volumeClaimTemplates&lt;/code&gt; section of a StatefulSet allows Kubernetes to create a PVC for each StatefulSet replica.&lt;/p&gt;

&lt;p&gt;For a single PostgreSQL replica, the claim is typically named using the StatefulSet name and ordinal, for example:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The actual name depends on the StatefulSet and claim-template names.&lt;/p&gt;

&lt;h3&gt;
  
  
  17.1 Example StorageClass
&lt;/h3&gt;

&lt;p&gt;A representative StorageClass for encrypted gp3 storage is:&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;storage.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;StorageClass&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;postgres-gp3&lt;/span&gt;
&lt;span class="na"&gt;provisioner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ebs.csi.aws.com&lt;/span&gt;
&lt;span class="na"&gt;volumeBindingMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;WaitForFirstConsumer&lt;/span&gt;
&lt;span class="na"&gt;allowVolumeExpansion&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;parameters&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;gp3&lt;/span&gt;
  &lt;span class="na"&gt;encrypted&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true"&lt;/span&gt;
&lt;span class="na"&gt;reclaimPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retain&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a reference example. If the cluster already has an appropriate StorageClass, use the actual one rather than creating a duplicate.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;WaitForFirstConsumer&lt;/code&gt; binding mode allows volume provisioning to account for the scheduling requirements of the consuming Pod.&lt;/p&gt;

&lt;p&gt;The reclaim policy deserves attention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Delete&lt;/code&gt; can remove the underlying dynamically provisioned volume when the claim is released, depending on the lifecycle and provisioner.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Retain&lt;/code&gt; preserves the underlying volume for manual recovery and cleanup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Retaining storage can reduce the chance of accidental data loss, but it can also leave billable volumes behind. Choose the policy deliberately.&lt;/p&gt;

&lt;h3&gt;
  
  
  17.2 Example PostgreSQL StatefulSet
&lt;/h3&gt;

&lt;p&gt;The following example illustrates the key configuration. Use the exact image version, credentials, probes, and initialization settings that your application requires.&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;StatefulSet&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;postgres&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;platform&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;serviceName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&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;1&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;postgres&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;postgres&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;postgres&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;postgres:16&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;5432&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;POSTGRES_DB&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&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;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB&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;POSTGRES_USER&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&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;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER&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;POSTGRES_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&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;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&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;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;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;1000m"&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;1Gi"&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;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"&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;livenessProbe&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;exec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;sh&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-c&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"&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;30&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;20&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;data&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;/var/lib/postgresql/data&lt;/span&gt;

  &lt;span class="na"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span class="pi"&gt;:&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;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;spec&lt;/span&gt;&lt;span class="pi"&gt;:&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="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gp3&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;20Gi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sample uses the existing &lt;code&gt;gp3&lt;/code&gt; StorageClass name from the environment. If you create the alternative &lt;code&gt;postgres-gp3&lt;/code&gt; class above, change &lt;code&gt;storageClassName&lt;/code&gt; accordingly.&lt;/p&gt;

&lt;p&gt;Check the PostgreSQL image version and data directory before using this manifest. PostgreSQL image upgrades need a planned database migration strategy; changing an image tag is not always a safe database upgrade.&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%2F4arh090696mhhdcswsp2.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%2F4arh090696mhhdcswsp2.png" alt=" " width="800" height="439"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For a real production deployment, also consider backup and restore, database upgrades, availability, monitoring, credentials rotation, and disaster recovery. A single database replica on one EBS volume is useful for demonstrating persistence, but it is not a highly available database architecture.&lt;/p&gt;

&lt;h3&gt;
  
  
  17.3 Create an internal PostgreSQL Service
&lt;/h3&gt;

&lt;p&gt;The backend needs a stable hostname through which it can reach PostgreSQL.&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;postgres&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;platform&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;postgres&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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&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;5432&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;5432&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apply the database resources:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Verify the StatefulSet and PVC:&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 statefulsets &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get pvc &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get pv
kubectl get storageclass
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PVC should eventually reach &lt;code&gt;Bound&lt;/code&gt;, and the PostgreSQL Pod should become ready.&lt;/p&gt;

&lt;p&gt;If the PVC remains &lt;code&gt;Pending&lt;/code&gt;, inspect its events:&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 pvc &lt;span class="nt"&gt;-n&lt;/span&gt; platform &amp;lt;claim-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also inspect the EBS CSI driver and the StorageClass. Storage failures may be caused by missing IAM permissions, unsupported topology, invalid StorageClass parameters, or an unhealthy CSI controller.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 8: Deploying the backend API
&lt;/h1&gt;

&lt;h2&gt;
  
  
  18. Backend Deployment
&lt;/h2&gt;

&lt;p&gt;A Deployment manages replicas of the backend application and replaces Pods when they fail.&lt;/p&gt;

&lt;p&gt;The backend image used by the capstone was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-capstone-prod/backend:v1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend listened on port &lt;code&gt;5000&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A representative Deployment is:&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;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;platform&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;backend&lt;/span&gt;

  &lt;span class="na"&gt;strategy&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;RollingUpdate&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

  &lt;span class="na"&gt;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;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;ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/platform-capstone-prod/backend:v1&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;DB_HOST&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&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;DB_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;5432"&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;DB_NAME&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&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;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_DB&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;DB_USER&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&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;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_USER&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;DB_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;valueFrom&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;secretKeyRef&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;postgres-secret&lt;/span&gt;
                  &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POSTGRES_PASSWORD&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;startupProbe&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;/health/live&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;failureThreshold&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&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;/health/live&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;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;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;/health/ready&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;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;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;allowPrivilegeEscalation&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;runAsNonRoot&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;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace &lt;code&gt;ACCOUNT_ID&lt;/code&gt; with the actual account ID when creating the manifest. A deployment pipeline can generate the image reference automatically.&lt;/p&gt;

&lt;p&gt;The security context assumes the image supports non-root execution. The image must be tested with the required UID, filesystem permissions, and any directories the application needs to write to.&lt;/p&gt;

&lt;h3&gt;
  
  
  18.1 Why probes matter
&lt;/h3&gt;

&lt;p&gt;The three probes serve different purposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Startup probe:&lt;/strong&gt; gives the application time to initialize before liveness checks take effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Liveness probe:&lt;/strong&gt; detects an application process that is no longer functioning correctly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Readiness probe:&lt;/strong&gt; determines whether the Pod should receive traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Readiness is particularly important when the backend depends on PostgreSQL. If the backend cannot access the database, it should not report itself as ready to handle database-dependent requests.&lt;/p&gt;

&lt;h3&gt;
  
  
  18.2 Create the backend Service
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;platform&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;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&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;5000&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Service exposes port 80 inside the cluster and forwards traffic to port 5000 on the backend Pods.&lt;/p&gt;

&lt;p&gt;Apply the 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;-f&lt;/span&gt; backend-deployment.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; backend-service.yaml
&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 deployment backend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; platform &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;backend
kubectl get service backend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl rollout status deployment/backend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the API from inside the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run backend-test &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;curlimages/curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://backend/health/live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The temporary Pod requires the ability to pull its image. If your cluster cannot reach the image registry, use an approved diagnostic image available in your environment.&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%2Fx6b7rlu3oonfaf6iup6i.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%2Fx6b7rlu3oonfaf6iup6i.png" alt=" " width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fev56ty72csbw93tb09ot.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%2Fev56ty72csbw93tb09ot.png" alt=" " width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The backend Deployment with two ready replicas, backend Service and port mapping and the successful backend health-check response.]&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 9: Deploying the frontend
&lt;/h1&gt;

&lt;h2&gt;
  
  
  19. Frontend Deployment
&lt;/h2&gt;

&lt;p&gt;The frontend was served through Nginx on port &lt;code&gt;8080&lt;/code&gt;. Its image was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;platform-capstone-prod/frontend:v2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend Deployment used two replicas.&lt;/p&gt;

&lt;p&gt;A representative manifest is:&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;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;platform&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;frontend&lt;/span&gt;

  &lt;span class="na"&gt;strategy&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;RollingUpdate&lt;/span&gt;
    &lt;span class="na"&gt;rollingUpdate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;maxSurge&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
      &lt;span class="na"&gt;maxUnavailable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;

  &lt;span class="na"&gt;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;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;ACCOUNT_ID.dkr.ecr.eu-west-1.amazonaws.com/platform-capstone-prod/frontend:v2&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;8080&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;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;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;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;8080&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;10&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;8080&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;20&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;allowPrivilegeEscalation&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;runAsNonRoot&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;capabilities&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;drop&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ALL&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sample assumes Nginx can serve the application using the selected non-root configuration. Validate the actual container image and configuration before applying it.&lt;/p&gt;

&lt;h2&gt;
  
  
  20. Create the frontend Service
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;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;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;platform&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;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;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&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;8080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The frontend Service provides a stable endpoint for the Pods. The Load Balancer Controller can then use this Service as the target for traffic configured through Ingress.&lt;/p&gt;

&lt;p&gt;Apply and validate:&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; frontend-deployment.yaml
kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; frontend-service.yaml

kubectl get deployment frontend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; platform &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;frontend
kubectl get service frontend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl rollout status deployment/frontend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the frontend from inside the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run frontend-test &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;curlimages/curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; curl &lt;span class="nt"&gt;-I&lt;/span&gt; http://frontend/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvxftudpdpohm4tafhjh1.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%2Fvxftudpdpohm4tafhjh1.png" alt=" " width="800" height="298"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The frontend Deployment showing two ready replicas and the frontend Service with port 80 targeting container port 8080.]&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 10: Installing the AWS Load Balancer Controller
&lt;/h1&gt;

&lt;h2&gt;
  
  
  21. Why the controller is required
&lt;/h2&gt;

&lt;p&gt;Kubernetes Ingress defines how HTTP traffic should be routed, but an Ingress resource does not create an AWS Application Load Balancer by itself.&lt;/p&gt;

&lt;p&gt;The AWS Load Balancer Controller watches Kubernetes resources and provisions or manages AWS load balancers based on their configuration.&lt;/p&gt;

&lt;p&gt;For this project, the controller was installed using Helm. The chart version was &lt;code&gt;1.14.0&lt;/code&gt;, with controller version &lt;code&gt;v2.14.0&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The controller also needs permission to manage the relevant AWS resources. The capstone used EKS Pod Identity for the controller's AWS identity configuration.&lt;/p&gt;

&lt;p&gt;The exact IAM policy, association, and Helm values must match the deployed cluster and controller version.&lt;/p&gt;

&lt;h3&gt;
  
  
  21.1 Verify Pod Identity support
&lt;/h3&gt;

&lt;p&gt;Check that the EKS Pod Identity Agent add-on is installed:&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 list-addons &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--cluster-name&lt;/span&gt; platform-capstone-prod &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the controller's service account and workload:&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 serviceaccounts &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  21.2 Install the controller with Helm
&lt;/h3&gt;

&lt;p&gt;For a new environment, first follow the AWS Load Balancer Controller documentation for creating the IAM policy, role, and Pod Identity association. The policy should be appropriate for the exact controller release.&lt;/p&gt;

&lt;p&gt;A representative Helm installation is:&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 eks https://aws.github.io/eks-charts
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then install the chart with the correct cluster name, region, VPC ID, service account, and controller image settings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm upgrade &lt;span class="nt"&gt;--install&lt;/span&gt; aws-load-balancer-controller &lt;span class="se"&gt;\&lt;/span&gt;
  eks/aws-load-balancer-controller &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; kube-system &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; 1.14.0 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;clusterName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;platform-capstone-prod &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;region&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;eu-west-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--set&lt;/span&gt; &lt;span class="nv"&gt;vpcId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_VPC_ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command is illustrative. Before using it, ensure the service account is associated with the correct Pod Identity role and that the Helm values match your IAM setup. A controller without sufficient AWS permissions may run but fail to create or reconcile load balancers.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout status deployment/aws-load-balancer-controller &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system

kubectl logs deployment/aws-load-balancer-controller &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;100
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Phase 11: Exposing the frontend with Ingress
&lt;/h1&gt;

&lt;h2&gt;
  
  
  22. Create the Ingress resource
&lt;/h2&gt;

&lt;p&gt;The capstone's public ALB routed the root path &lt;code&gt;/&lt;/code&gt; to the frontend Service over HTTP port 80.&lt;/p&gt;

&lt;p&gt;A representative Ingress manifest is:&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;networking.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Ingress&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;platform-ingress&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;platform&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;alb.ingress.kubernetes.io/scheme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;internet-facing&lt;/span&gt;
    &lt;span class="na"&gt;alb.ingress.kubernetes.io/target-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ip&lt;/span&gt;
    &lt;span class="na"&gt;alb.ingress.kubernetes.io/listen-ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[{"HTTP":80}]'&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;ingressClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alb&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;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/&lt;/span&gt;
            &lt;span class="na"&gt;pathType&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prefix&lt;/span&gt;
            &lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="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;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                  &lt;span class="na"&gt;number&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This example creates an internet-facing HTTP listener. It does not configure HTTPS or a TLS certificate. For production, use HTTPS, a suitable certificate in AWS Certificate Manager, and a planned HTTP-to-HTTPS redirect.&lt;/p&gt;

&lt;p&gt;The target type &lt;code&gt;ip&lt;/code&gt; allows the load balancer to target Pod IPs directly through the controller's target group configuration.&lt;/p&gt;

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

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

&lt;/div&gt;



&lt;p&gt;Check its 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 ingress &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl describe ingress platform-ingress &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frhig2pilnny71aw5siyz.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%2Frhig2pilnny71aw5siyz.png" alt=" " width="799" height="415"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The ALB DNS name should appear in the Ingress address after the controller successfully reconciles the resource.&lt;/p&gt;

&lt;h3&gt;
  
  
  22.1 Test the public frontend
&lt;/h3&gt;

&lt;p&gt;Obtain the hostname:&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;ALB_DNS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get ingress platform-ingress &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; platform &lt;span class="se"&gt;\&lt;/span&gt;
  &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;"&lt;/span&gt;&lt;span class="nv"&gt;$ALB_DNS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test the HTTP endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ALB_DNS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwhsu9488rqyjwhos5ugm.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%2Fwhsu9488rqyjwhos5ugm.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;http://&lt;/code&gt; when the Ingress only defines an HTTP listener. Attempting HTTPS without an HTTPS listener and certificate will not test the configuration correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  22.2 Troubleshooting a public ALB
&lt;/h3&gt;

&lt;p&gt;If the application does not load, check the problem layer by layer.&lt;/p&gt;

&lt;p&gt;First, inspect Ingress events:&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 ingress platform-ingress &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inspect the controller logs:&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 deployment/aws-load-balancer-controller &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; kube-system &lt;span class="nt"&gt;--tail&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the frontend Pods and Service:&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; platform &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;frontend
kubectl get service frontend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get endpoints frontend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, inspect the ALB target groups and health status in the AWS Console or through the ELBv2 API.&lt;/p&gt;

&lt;p&gt;A healthy ALB DNS record does not guarantee that the target group is healthy. Common causes include security group restrictions, incorrect health-check paths, a Service targeting the wrong port, or Pods that are not ready.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 12: Resource management and autoscaling
&lt;/h1&gt;

&lt;h2&gt;
  
  
  23. Why resource requests and limits matter
&lt;/h2&gt;

&lt;p&gt;Kubernetes uses resource requests and limits to help schedule workloads and control their resource consumption.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Requests&lt;/strong&gt; tell the scheduler the amount of CPU and memory the container needs for scheduling purposes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits&lt;/strong&gt; cap resource consumption. CPU limits can throttle a process, while exceeding a memory limit can cause the container to be terminated.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Requests and limits should reflect observed application behavior, not arbitrary values. The examples in this document are starting points, not universal production recommendations.&lt;/p&gt;

&lt;p&gt;Check resource configuration in the live 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 get deployments &lt;span class="nt"&gt;-n&lt;/span&gt; platform &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
kubectl describe pods &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The capstone assessment required workloads to have resource controls and health probes. Review every workload, including database workloads, to verify that the final manifests satisfy the requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  24. Configure Horizontal Pod Autoscaling
&lt;/h2&gt;

&lt;p&gt;The Horizontal Pod Autoscaler (HPA) adjusts the number of replicas in response to observed resource utilization.&lt;/p&gt;

&lt;p&gt;The capstone configured CPU-based scaling with a target of 70%, a minimum of two replicas, and a maximum of five replicas.&lt;/p&gt;

&lt;p&gt;A representative HPA manifest for the backend is:&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;autoscaling/v2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HorizontalPodAutoscaler&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend-hpa&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;platform&lt;/span&gt;

&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scaleTargetRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;

  &lt;span class="na"&gt;minReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;

  &lt;span class="na"&gt;metrics&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Resource&lt;/span&gt;
      &lt;span class="na"&gt;resource&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cpu&lt;/span&gt;
        &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Utilization&lt;/span&gt;
          &lt;span class="na"&gt;averageUtilization&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;70&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For CPU utilization-based scaling, resource requests must be configured. If requests are missing, the HPA may be unable to calculate the intended utilization percentage.&lt;/p&gt;

&lt;p&gt;The Metrics Server must also be healthy.&lt;/p&gt;

&lt;p&gt;Verify metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl top nodes
kubectl top pods &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the HPA:&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 hpa &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl describe hpa backend-hpa &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HPA output includes current and target utilization, minimum and maximum replicas, and the current replica count.&lt;/p&gt;

&lt;h3&gt;
  
  
  24.1 Test autoscaling
&lt;/h3&gt;

&lt;p&gt;A load test can help demonstrate whether the HPA reacts to demand. Use a controlled test environment and stop the test after collecting the evidence.&lt;/p&gt;

&lt;p&gt;For example, a temporary load generator can send repeated requests to the backend Service. The exact command depends on the tools available in the cluster and the application's endpoints.&lt;/p&gt;

&lt;p&gt;While the test is running, observe:&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 hpa &lt;span class="nt"&gt;-n&lt;/span&gt; platform &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In another terminal:&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; platform &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;backend &lt;span class="nt"&gt;--watch&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the test, confirm that the replica count stabilizes and eventually scales down when utilization falls. Scaling is not necessarily immediate because Kubernetes uses sampling intervals, stabilization behavior, and scheduling time.&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%2F30ueoqrl6k16fqkpz341.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%2F30ueoqrl6k16fqkpz341.png" alt=" " width="800" height="365"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fuqzam2orasqvfn5y3f8t.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%2Fuqzam2orasqvfn5y3f8t.png" alt=" " width="800" height="162"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: Insert &lt;code&gt;kubectl top pods -n platform&lt;/code&gt; showing resource metrics.]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fpf6ll6ggxt4grwe5oxcl.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%2Fpf6ll6ggxt4grwe5oxcl.png" alt=" " width="800" height="212"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: Insert &lt;code&gt;kubectl get hpa -n platform&lt;/code&gt; showing the target CPU utilization.]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F1qdz0pvd3ruffen4aglz.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%2F1qdz0pvd3ruffen4aglz.png" alt=" " width="798" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&amp;amp;&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%2Fyl2dthz7r1trmpp05993.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%2Fyl2dthz7r1trmpp05993.png" alt=" " width="798" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The scaling test showing replicas increasing.]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqu0p75gqzi0lzuk2fvs7.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%2Fqu0p75gqzi0lzuk2fvs7.png" alt=" " width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The final HPA status after the test.]&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 13: Validating PostgreSQL persistence and recovery
&lt;/h1&gt;

&lt;h2&gt;
  
  
  25. Why Pod recovery is not the same as data recovery
&lt;/h2&gt;

&lt;p&gt;Kubernetes can recreate a Pod, but whether its data survives depends on where the data is stored.&lt;/p&gt;

&lt;p&gt;Data written only to a container's writable layer is not a reliable persistence mechanism. When a container or Pod is replaced, that local state can disappear.&lt;/p&gt;

&lt;p&gt;In this capstone, PostgreSQL stored its data on an EBS-backed persistent volume. The PVC provided the durable storage request, and the StatefulSet associated that claim with the PostgreSQL workload.&lt;/p&gt;

&lt;p&gt;The validation goal was to demonstrate that the database data remained available after the PostgreSQL Pod was deleted and recreated.&lt;/p&gt;

&lt;h3&gt;
  
  
  25.1 Insert a test record
&lt;/h3&gt;

&lt;p&gt;Connect to PostgreSQL using the database credentials and tooling configured for the environment. One approach is to use &lt;code&gt;kubectl exec&lt;/code&gt; with &lt;code&gt;psql&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; postgres-0 &lt;span class="nt"&gt;-n&lt;/span&gt; platform &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-U&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_USER&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$POSTGRES_DB&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The environment variables in your local shell will not automatically be available inside the Pod. For this reason, substitute the correct database name and user from the configured Secret, or use a secure method to pass them. Do not put the database password in a public screenshot.&lt;/p&gt;

&lt;p&gt;Create a test table and insert a row:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;persistence_test&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="nb"&gt;SERIAL&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="nb"&gt;TEXT&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;persistence_test&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Capstone persistence validation'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;Record the returned row for comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  25.2 Delete the PostgreSQL Pod
&lt;/h3&gt;

&lt;p&gt;Delete the Pod without deleting the StatefulSet or PVC:&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 postgres-0 &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;Once it is ready, query the test record again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; postgres-0 &lt;span class="nt"&gt;-n&lt;/span&gt; platform &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  psql &lt;span class="nt"&gt;-U&lt;/span&gt; YOUR_DB_USER &lt;span class="nt"&gt;-d&lt;/span&gt; YOUR_DB_NAME &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"SELECT * FROM persistence_test;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the example database values with the actual values from the configured Secret.&lt;/p&gt;

&lt;p&gt;The test succeeds when the Pod has been recreated and the previously inserted row is still present.&lt;/p&gt;

&lt;h3&gt;
  
  
  25.3 Verify the PVC remains bound
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pvc &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get pv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PVC should remain bound to the volume associated with PostgreSQL.&lt;/p&gt;

&lt;p&gt;This test demonstrates persistence across Pod recreation. It does not, by itself, prove that backups can be restored, that a volume survives every infrastructure failure, or that the database is highly available. Those require separate tests.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 14: Application and platform validation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  26. Validate the cluster and workloads
&lt;/h2&gt;

&lt;p&gt;Validation should cover both the underlying infrastructure and the application behavior.&lt;/p&gt;

&lt;p&gt;Start by checking the nodes:&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;span class="nt"&gt;-o&lt;/span&gt; wide
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then inspect all resources in the application namespace:&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;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check the database storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pvc &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get pv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check autoscaling and metrics:&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 hpa &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl top pods &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inspect recent events if something is not healthy:&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 events &lt;span class="nt"&gt;-n&lt;/span&gt; platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--sort-by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;.metadata.creationTimestamp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyjdyncuo79xiw539vnot.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%2Fyjdyncuo79xiw539vnot.png" alt=" " width="798" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The desired state was two frontend replicas, two backend replicas under normal conditions, and one PostgreSQL replica with a bound PVC. HPA could adjust the backend replica count between two and five in response to CPU utilization.&lt;/p&gt;

&lt;p&gt;The actual replica count at any particular time depends on scaling activity and workload conditions.&lt;/p&gt;

&lt;h2&gt;
  
  
  27. Validate the frontend and backend
&lt;/h2&gt;

&lt;p&gt;For the public frontend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-I&lt;/span&gt; &lt;span class="s2"&gt;"http://&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ALB_DNS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For the backend, test its health endpoints through the internal Service or an approved diagnostic 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 run backend-test &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;curlimages/curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://backend/health/live
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then test readiness:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl run backend-ready-test &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--namespace&lt;/span&gt; platform &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--restart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Never &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--image&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;curlimages/curl &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--&lt;/span&gt; curl &lt;span class="nt"&gt;-i&lt;/span&gt; http://backend/health/ready
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;200&lt;/code&gt; response is useful evidence, but readiness is meaningful only if the endpoint checks the dependencies required by the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  28. Validate a rolling update
&lt;/h2&gt;

&lt;p&gt;A Deployment can replace application Pods gradually when its image or Pod template changes.&lt;/p&gt;

&lt;p&gt;For a controlled test, publish a new immutable image tag and update the Deployment to use it. Avoid changing a production tag to point to a different image without a controlled release process.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;set &lt;/span&gt;image deployment/backend &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nv"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;YOUR_BACKEND_ECR_URI:v2 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Monitor the rollout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl rollout status deployment/backend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl rollout &lt;span class="nb"&gt;history &lt;/span&gt;deployment/backend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the update fails, inspect the rollout and Pod events before considering a rollback:&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 deployment backend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
kubectl get pods &lt;span class="nt"&gt;-n&lt;/span&gt; platform &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;backend
kubectl rollout undo deployment/backend &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example uses &lt;code&gt;v2&lt;/code&gt; as a placeholder. The tag must exist in ECR before applying it.&lt;/p&gt;

&lt;p&gt;A rolling-update strategy with &lt;code&gt;maxUnavailable: 0&lt;/code&gt; and &lt;code&gt;maxSurge: 1&lt;/code&gt; aims to keep the desired number of available replicas during a rollout, provided there is sufficient capacity and the readiness probes accurately identify healthy Pods. It does not guarantee zero downtime under every failure condition.&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 15: Terraform validation and infrastructure documentation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  29. Validate Terraform before final delivery
&lt;/h2&gt;

&lt;p&gt;Infrastructure validation is an important part of the delivery process.&lt;/p&gt;

&lt;p&gt;From the production Terraform directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ~/platform-capstone/terraform/environments/prod

terraform &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;-check&lt;/span&gt; &lt;span class="nt"&gt;-recursive&lt;/span&gt;
terraform validate
terraform plan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a successful deployment, a plan that reports no changes is useful evidence that the configuration matches the infrastructure Terraform is tracking at that moment.&lt;/p&gt;

&lt;p&gt;However, a clean plan does not prove the application is healthy. Runtime checks remain necessary.&lt;/p&gt;

&lt;p&gt;If you have already destroyed the infrastructure, do not run &lt;code&gt;terraform apply&lt;/code&gt; simply to make the plan return to zero changes. A destroy plan or a plan proposing resource creation is expected when the infrastructure no longer exists. Review the state and the intended lifecycle before proceeding.&lt;/p&gt;

&lt;h2&gt;
  
  
  30. Generate a Terraform dependency graph
&lt;/h2&gt;

&lt;p&gt;Terraform can generate a graph showing relationships between resources.&lt;/p&gt;

&lt;p&gt;Run this from the directory containing the environment's Terraform root 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="nb"&gt;cd&lt;/span&gt; ~/platform-capstone/terraform/environments/prod

terraform init
terraform graph | dot &lt;span class="nt"&gt;-Tsvg&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/platform-capstone/infrastructure.svg
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the SVG in Windows from WSL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;explorer.exe &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;wslpath &lt;span class="nt"&gt;-w&lt;/span&gt; ~/platform-capstone/infrastructure.svg&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The graph is a dependency graph, not a complete application architecture diagram. For the blog post, a separate diagram showing users, ALB, Kubernetes Services, Pods, PostgreSQL, and EBS storage is more useful for readers who are new to the project.&lt;/p&gt;

&lt;p&gt;If the infrastructure has been destroyed, Terraform graph generation can still be useful when the configuration and provider setup remain available locally, but it does not prove that AWS resources are currently running.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5n2mqpqhgn0xm8uz4xf4.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%2F5n2mqpqhgn0xm8uz4xf4.png" alt=" " width="799" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The Terraform validation output.]&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 16: GitHub and project delivery
&lt;/h1&gt;

&lt;h2&gt;
  
  
  31. Version control and repository hygiene
&lt;/h2&gt;

&lt;p&gt;Git provides a history of infrastructure and application changes. GitHub makes the project available for review and future reuse.&lt;/p&gt;

&lt;p&gt;The capstone reached a final commit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;76c49b1 feat: deploy AWS Kubernetes platform capstone
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before publishing, confirm that the hash still matches your repository and that the latest changes are committed.&lt;/p&gt;

&lt;p&gt;Useful commands include:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status
git log &lt;span class="nt"&gt;--oneline&lt;/span&gt; &lt;span class="nt"&gt;-5&lt;/span&gt;
git diff &lt;span class="nt"&gt;--stat&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check whether the current branch is ahead of or behind its upstream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git status &lt;span class="nt"&gt;-sb&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Do not publish Terraform state files, cloud credentials, production database passwords, or other sensitive configuration. A secret file being ignored today does not prove that it was never committed in an earlier commit.&lt;/p&gt;

&lt;p&gt;If a secret was committed, remove it from the repository history as appropriate and rotate the exposed credential. Removing the file from the latest commit alone does not make an exposed secret safe.&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%2F82jm2n9ah6cp60mj1kkk.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%2F82jm2n9ah6cp60mj1kkk.png" alt=" " width="800" height="393"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The GitHub repository overview.]&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F97ru7u4jbnuqyvjl3m13.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%2F97ru7u4jbnuqyvjl3m13.png" alt=" " width="799" height="367"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;[Screenshot: The final commit and clean Git status.]&lt;/strong&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Phase 17: Infrastructure cleanup
&lt;/h1&gt;

&lt;h2&gt;
  
  
  32. Why teardown matters
&lt;/h2&gt;

&lt;p&gt;AWS resources can continue to incur charges even after an application is no longer being tested. For example, NAT Gateways, worker nodes, load balancers, EBS volumes, and container images can all contribute to cost.&lt;/p&gt;

&lt;p&gt;The capstone was therefore torn down after the validation evidence had been collected.&lt;/p&gt;

&lt;p&gt;The cleanup process also needed to preserve the remote Terraform state in S3 so that the deployment record remained available.&lt;/p&gt;

&lt;h3&gt;
  
  
  32.1 Remove the Ingress first
&lt;/h3&gt;

&lt;p&gt;Deleting the Ingress gives the AWS Load Balancer Controller an opportunity to remove the ALB and its associated resources.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl delete ingress &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; platform
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wait for the ALB to disappear from AWS before proceeding, allowing time for the controller to reconcile the deletion.&lt;/p&gt;

&lt;h3&gt;
  
  
  32.2 Delete the application namespace
&lt;/h3&gt;



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

&lt;/div&gt;



&lt;p&gt;This removes namespaced application resources, including Deployments, Services, StatefulSets, and PVCs, subject to Kubernetes finalizers and storage lifecycle policies.&lt;/p&gt;

&lt;p&gt;Check the namespace:&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 namespaces
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Warning:&lt;/strong&gt; deleting a namespace can remove the PVC that holds database data. Ensure that the data is no longer needed and that any required backup or evidence has been preserved before doing this.&lt;/p&gt;

&lt;h3&gt;
  
  
  32.3 Review Terraform's destroy plan
&lt;/h3&gt;

&lt;p&gt;From the production Terraform root:&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; ~/platform-capstone/terraform/environments/prod

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

&lt;/div&gt;



&lt;p&gt;Inspect the planned actions. Ensure that the plan does not include deleting the remote S3 state bucket. The S3 backend bucket was managed separately from the production application infrastructure.&lt;/p&gt;

&lt;p&gt;If the plan is correct, proceed with the destroy operation:&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;Review the confirmation prompt carefully before approving.&lt;/p&gt;

&lt;p&gt;Do not destroy the bootstrap configuration if it manages the S3 bucket that stores the remote state.&lt;/p&gt;

&lt;h3&gt;
  
  
  32.4 Verify the cleanup
&lt;/h3&gt;

&lt;p&gt;The audit used AWS CLI commands to check for remaining EKS clusters, EC2 instances, load balancers, NAT Gateways, Elastic IPs, EBS volumes, snapshots, ECR repositories, and other related resources.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws eks list-clusters &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1

aws ec2 describe-instances &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filters&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;instance-state-name,Values&lt;span class="o"&gt;=&lt;/span&gt;pending,running,stopping,stopped

aws elbv2 describe-load-balancers &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1

aws ec2 describe-nat-gateways &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--filter&lt;/span&gt; &lt;span class="nv"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;state,Values&lt;span class="o"&gt;=&lt;/span&gt;pending,available,deleting,failed

aws ec2 describe-addresses &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1

aws ec2 describe-volumes &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1

aws ecr describe-repositories &lt;span class="nt"&gt;--region&lt;/span&gt; eu-west-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Empty results in these commands indicate that the corresponding queries found no matching resources.&lt;/p&gt;

&lt;p&gt;In the capstone cleanup audit, the main infrastructure was absent from the checked regions. The two EC2 worker instances appeared as terminated, while the checks found no EKS clusters, load balancers, NAT Gateways, Elastic IPs, EBS volumes, snapshots, or ECR repositories in the primary region.&lt;/p&gt;

&lt;p&gt;The audit also found no matching capstone IAM roles and no remaining non-default security groups or network interfaces in the primary region.&lt;/p&gt;

&lt;p&gt;The Terraform state remained in the S3 bucket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;s3://platform-capstone-terraform-state-911xxxx3075/platform-capstone/prod/terraform.tfstate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The bucket was intentionally preserved. S3 is not automatically free, so review storage, requests, versioned objects, and billing periodically.&lt;/p&gt;

&lt;p&gt;These checks provide strong evidence that the core capstone infrastructure was removed, but they do not prove that every AWS service in every region is empty. Review any remaining resources before assuming there are no further charges.&lt;/p&gt;




&lt;h1&gt;
  
  
  33. Results and outcomes
&lt;/h1&gt;

&lt;p&gt;The capstone brought the infrastructure, application deployment, and validation stages together into one practical platform engineering exercise.&lt;/p&gt;

&lt;p&gt;The main outcomes were:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Infrastructure as Code:&lt;/strong&gt; AWS infrastructure was defined and provisioned through Terraform modules and a production environment configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network separation:&lt;/strong&gt; the VPC included public and private subnets across two Availability Zones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed Kubernetes:&lt;/strong&gt; the Amazon EKS cluster ran two managed &lt;code&gt;t3.medium&lt;/code&gt; worker nodes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container delivery:&lt;/strong&gt; separate frontend and backend images were built and published to Amazon ECR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application orchestration:&lt;/strong&gt; Kubernetes Deployments and Services managed the frontend and backend workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent storage:&lt;/strong&gt; PostgreSQL used a StatefulSet and a bound PVC backed by encrypted gp3 EBS storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;External access:&lt;/strong&gt; an AWS Load Balancer Controller reconciled the Ingress configuration into an internet-facing ALB.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health validation:&lt;/strong&gt; frontend and backend health checks were used to validate application readiness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autoscaling:&lt;/strong&gt; CPU-based HPA configuration used a 70% target and a replica range of two to five.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recovery testing:&lt;/strong&gt; a PostgreSQL Pod was deleted and recreated, and the test record remained available on persistent storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery documentation:&lt;/strong&gt; the project was committed to GitHub and documented for future reference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resource cleanup:&lt;/strong&gt; the primary AWS infrastructure was removed after testing, while the S3 state bucket was preserved.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These outcomes demonstrate how infrastructure provisioning, containerization, Kubernetes orchestration, persistence, networking, and validation combine into a single cloud platform.&lt;/p&gt;

&lt;p&gt;They also highlight an important engineering lesson: a resource being present is not the same as it being healthy. A running Pod does not prove the application can reach its database. A created Ingress does not prove the ALB targets are healthy. A bound PVC does not prove that a recovery process works. Each layer needs its own test.&lt;/p&gt;




&lt;h1&gt;
  
  
  34. Lessons learned
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Infrastructure should be reproducible
&lt;/h2&gt;

&lt;p&gt;Terraform makes infrastructure easier to review and recreate, but only when the configuration, variables, provider versions, and remote state are maintained carefully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application health must include dependencies
&lt;/h2&gt;

&lt;p&gt;A backend can return a successful liveness response while its database is unavailable. Liveness and readiness should represent different conditions, and readiness should verify the dependencies required to serve requests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persistent storage requires lifecycle awareness
&lt;/h2&gt;

&lt;p&gt;Stateful workloads need more than a PVC declaration. Storage classes, volume binding, access modes, encryption, reclaim policies, backup, and restoration all influence the safety and lifecycle of database data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autoscaling depends on observability and resource requests
&lt;/h2&gt;

&lt;p&gt;An HPA manifest is only one part of autoscaling. The Metrics Server must be working, CPU requests must be set, and the cluster needs sufficient capacity to schedule additional replicas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Networking should be tested layer by layer
&lt;/h2&gt;

&lt;p&gt;When an application is not reachable, check the listener protocol, Ingress events, controller logs, Service endpoints, target health, and security group rules. This is more effective than repeatedly changing configuration without identifying the failing layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup is part of engineering
&lt;/h2&gt;

&lt;p&gt;A project is not complete merely because it works. Reviewing the resources created, preserving the necessary state, removing billable infrastructure, and documenting the final status are part of responsible cloud operations.&lt;/p&gt;




&lt;h1&gt;
  
  
  35. Future improvements
&lt;/h1&gt;

&lt;p&gt;There are several areas in which this capstone could be extended.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Add HTTPS using AWS Certificate Manager.&lt;/li&gt;
&lt;li&gt;Redirect HTTP to HTTPS.&lt;/li&gt;
&lt;li&gt;Store production secrets in AWS Secrets Manager or another managed secret system.&lt;/li&gt;
&lt;li&gt;Review least-privilege IAM permissions and security group rules.&lt;/li&gt;
&lt;li&gt;Add image vulnerability scanning and container policy checks.&lt;/li&gt;
&lt;li&gt;Consider Kubernetes NetworkPolicies where the networking implementation supports them.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add automated PostgreSQL backups and restore testing.&lt;/li&gt;
&lt;li&gt;Document database upgrade and recovery procedures.&lt;/li&gt;
&lt;li&gt;Evaluate a managed database such as Amazon RDS for a production architecture.&lt;/li&gt;
&lt;li&gt;Add disruption budgets and scheduling constraints where appropriate.&lt;/li&gt;
&lt;li&gt;Test rolling updates under realistic traffic conditions.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Centralize application and controller logs.&lt;/li&gt;
&lt;li&gt;Add application-level metrics and dashboards.&lt;/li&gt;
&lt;li&gt;Define service-level indicators for availability and latency.&lt;/li&gt;
&lt;li&gt;Alert on unhealthy Pods, failed rollouts, and storage issues.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Delivery automation
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Add a GitHub Actions pipeline to run Terraform formatting and validation.&lt;/li&gt;
&lt;li&gt;Build and scan Docker images on pull requests.&lt;/li&gt;
&lt;li&gt;Push versioned images to ECR after approved builds.&lt;/li&gt;
&lt;li&gt;Deploy through a controlled release workflow.&lt;/li&gt;
&lt;li&gt;Keep credentials out of CI logs and repository files.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Review NAT Gateway architecture and data transfer.&lt;/li&gt;
&lt;li&gt;Evaluate the worker node size against observed workload requirements.&lt;/li&gt;
&lt;li&gt;Set AWS Budgets and billing alerts.&lt;/li&gt;
&lt;li&gt;Define a documented cleanup procedure for non-production environments.&lt;/li&gt;
&lt;li&gt;Periodically review ECR images, S3 versions, and retained EBS volumes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These improvements would move the project closer to a more complete platform engineering implementation.&lt;/p&gt;




&lt;h1&gt;
  
  
  36. Final checklist
&lt;/h1&gt;

&lt;p&gt;Use this checklist when reviewing the project or preparing the next version.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS infrastructure
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] EKS cluster provisioned&lt;/li&gt;
&lt;li&gt;[x] VPC and networking configured&lt;/li&gt;
&lt;li&gt;[x] Worker nodes provisioned&lt;/li&gt;
&lt;li&gt;[x] ECR repositories created&lt;/li&gt;
&lt;li&gt;[x] EBS volume provisioned&lt;/li&gt;
&lt;li&gt;[x] Application Load Balancer created&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Kubernetes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] Worker nodes reached &lt;code&gt;Ready&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[x] Workloads and Services deployed&lt;/li&gt;
&lt;li&gt;[x] Resource metrics checked&lt;/li&gt;
&lt;li&gt;[x] HPA configured and tested&lt;/li&gt;
&lt;li&gt;[x] PostgreSQL PVC bound&lt;/li&gt;
&lt;li&gt;[x] Ingress configured&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Validation
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] Public frontend tested&lt;/li&gt;
&lt;li&gt;[x] Backend readiness tested&lt;/li&gt;
&lt;li&gt;[x] Database persistence and recovery tested&lt;/li&gt;
&lt;li&gt;[x] HPA scaling test completed&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Delivery
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] Terraform validation performed&lt;/li&gt;
&lt;li&gt;[x] GitHub repository maintained&lt;/li&gt;
&lt;li&gt;[x] Final commit recorded&lt;/li&gt;
&lt;li&gt;[x] Clean Git status checked&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;[x] Main capstone infrastructure removed&lt;/li&gt;
&lt;li&gt;[x] Primary-region resource audit performed&lt;/li&gt;
&lt;li&gt;[x] Additional regional checks performed&lt;/li&gt;
&lt;li&gt;[x] Terraform state preserved in S3&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The checked boxes above record the project milestones reported during the capstone. They should be retained as evidence of completed work, while future improvements should be tracked separately.&lt;/p&gt;




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

&lt;p&gt;This capstone was an opportunity to work through the lifecycle of a cloud-native application platform: defining infrastructure, building images, provisioning Kubernetes, deploying services, persisting data, exposing the application, testing scaling and recovery, and cleaning up resources responsibly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; [&lt;a href="https://github.com/Jahmeeu-Cloud/platform-capstone" rel="noopener noreferrer"&gt;https://github.com/Jahmeeu-Cloud/platform-capstone&lt;/a&gt;]&lt;/p&gt;

&lt;p&gt;The biggest takeaway was the importance of understanding the boundaries between the different layers. Terraform manages infrastructure; Docker packages the application; ECR stores the images; Kubernetes manages workloads; the AWS Load Balancer Controller connects Kubernetes networking resources to AWS load balancers; and persistent storage allows the database to retain data beyond the lifetime of a Pod.&lt;/p&gt;

&lt;p&gt;Each component serves a different purpose, and the platform only works as expected when those components are configured and validated together.&lt;/p&gt;

&lt;p&gt;This is a foundation that can be extended into more advanced work involving CI/CD, GitOps, managed secrets, observability, policy enforcement, and platform self-service.&lt;/p&gt;

&lt;p&gt;The aim is not simply to deploy an application once. It is to build an environment that another engineer can understand, reproduce, test, and improve.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Project status:&lt;/strong&gt; The core capstone deployment and validation milestones were completed, and the main AWS infrastructure was subsequently removed to avoid unnecessary ongoing resource charges. The Terraform state was retained in Amazon S3 for future reference.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>docker</category>
      <category>postgressql</category>
    </item>
    <item>
      <title>Resilient Kubernetes Application</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Wed, 07 Oct 2026 16:17:33 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/resilient-kubernetes-application-1875</link>
      <guid>https://dev.to/jamiu_cloud/resilient-kubernetes-application-1875</guid>
      <description>&lt;p&gt;A production-style Kubernetes resilience and platform engineering project deployed on &lt;strong&gt;Amazon EKS&lt;/strong&gt;, using &lt;strong&gt;Helm, Kubernetes StatefulSets, persistent EBS storage, HPA, ALB Ingress, Redis, PostgreSQL, and containerized application workloads&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The project focuses on more than simply deploying an application. It demonstrates how to &lt;strong&gt;identify, troubleshoot, recover from, and harden common Kubernetes failure scenarios&lt;/strong&gt;, particularly around persistent storage, application health checks, autoscaling, and Helm-based deployment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This project implements a resilient multi-component application on Amazon EKS with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Frontend application&lt;/li&gt;
&lt;li&gt;Backend application&lt;/li&gt;
&lt;li&gt;PostgreSQL database&lt;/li&gt;
&lt;li&gt;Redis cache&lt;/li&gt;
&lt;li&gt;Persistent EBS-backed PostgreSQL storage&lt;/li&gt;
&lt;li&gt;Kubernetes Services&lt;/li&gt;
&lt;li&gt;AWS Application Load Balancer (ALB) Ingress&lt;/li&gt;
&lt;li&gt;Horizontal Pod Autoscaler (HPA)&lt;/li&gt;
&lt;li&gt;Helm-based application packaging&lt;/li&gt;
&lt;li&gt;Environment-specific Helm values&lt;/li&gt;
&lt;li&gt;Kubernetes health probes&lt;/li&gt;
&lt;li&gt;PersistentVolume (PV) and PersistentVolumeClaim (PVC) management&lt;/li&gt;
&lt;li&gt;PostgreSQL storage recovery&lt;/li&gt;
&lt;li&gt;Storage retention testing&lt;/li&gt;
&lt;li&gt;AWS EBS volume optimization&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project also includes hands-on troubleshooting of several realistic Kubernetes issues encountered during deployment.&lt;/p&gt;




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



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                         Internet
                            │
                            ▼
                 AWS Application Load Balancer
                            │
                            ▼
                  Kubernetes Ingress
                 resilient-app-ingress
                     ┌──────┴──────┐
                     │             │
                    /              /api
                     │             │
                     ▼             ▼
              Frontend Service   Backend Service
                     │             │
                     ▼             ▼
               Frontend Pods    Backend Pods
                  2 replicas     2+ replicas
                                    │
                                    │
                               HPA: 2–5
                                    │
                                    ▼
                              Application API


                 ┌─────────────────────────┐
                 │     resilient-app       │
                 │       namespace         │
                 └────────────┬────────────┘
                              │
                 ┌────────────┴────────────┐
                 │                         │
                 ▼                         ▼
          PostgreSQL StatefulSet       Redis Deployment
                 │                         │
                 ▼                         ▼
              PostgreSQL               Redis Service
                 │
                 ▼
                PVC
                 │
                 ▼
                PV
                 │
                 ▼
           AWS EBS Volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Technology Stack
&lt;/h1&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Technology&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;Amazon EKS&lt;/td&gt;
&lt;td&gt;Managed Kubernetes cluster&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kubernetes&lt;/td&gt;
&lt;td&gt;Container orchestration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Helm&lt;/td&gt;
&lt;td&gt;Application packaging and configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS ALB&lt;/td&gt;
&lt;td&gt;External application ingress&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL 15&lt;/td&gt;
&lt;td&gt;Persistent relational database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Application cache&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS EBS&lt;/td&gt;
&lt;td&gt;Persistent block storage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HPA&lt;/td&gt;
&lt;td&gt;Backend autoscaling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Docker/container images&lt;/td&gt;
&lt;td&gt;Application workloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;YAML&lt;/td&gt;
&lt;td&gt;Kubernetes and Helm configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;kubectl&lt;/td&gt;
&lt;td&gt;Cluster administration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AWS CLI&lt;/td&gt;
&lt;td&gt;EBS and AWS resource management&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fett4s2my6gx886j7qrg0.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%2Fett4s2my6gx886j7qrg0.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Repository Structure
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.
├── charts/
├── docs/
│   └── screenshots/
└── manifests/
    ├── backend-hpa.yaml
    ├── backend.yaml
    ├── cache.yaml
    ├── configmap-secret.yaml
    ├── database.yml
    ├── frontend.yaml
    ├── helm/
    │   ├── resilient-app/
    │   │   ├── Chart.yaml
    │   │   ├── postgres-recovery-pvc.yaml
    │   │   ├── templates/
    │   │   │   ├── backend-hpa.yaml
    │   │   │   ├── backend.yaml
    │   │   │   ├── cache.yaml
    │   │   │   ├── configmap-secret.yaml
    │   │   │   ├── database.yaml
    │   │   │   ├── frontend.yaml
    │   │   │   └── ingress.yaml
    │   │   ├── values-dev.yaml
    │   │   ├── values-staging.yaml
    │   │   ├── values-prod.yaml
    │   │   └── values.yaml
    │   └── values-prod.yaml
    ├── ingress.yaml
    ├── namespace.yml
    └── storageclass.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;The repository contains both the original Kubernetes manifests and the Helm-based implementation used to package the application.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Kubernetes Components
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Frontend
&lt;/h2&gt;

&lt;p&gt;The frontend is deployed as a Kubernetes Deployment and exposed internally through:&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%2Fn11w2xmjv1g8vypvu6iq.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%2Fn11w2xmjv1g8vypvu6iq.png" alt=" " width="800" height="419"&gt;&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;frontend-service:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqzpiveu4h88r1rdvvca6.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%2Fqzpiveu4h88r1rdvvca6.png" alt=" " width="800" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The production configuration supports multiple replicas to improve availability.&lt;/p&gt;

&lt;p&gt;The application was validated through Kubernetes port forwarding and returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kubernetes Resilience Demo - Frontend Active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ffz38igeuisluuaalw14f.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%2Ffz38igeuisluuaalw14f.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Backend
&lt;/h2&gt;

&lt;p&gt;The backend is deployed as a Kubernetes Deployment and exposed through:&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%2F51otpwbq5a59wu6s70zi.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%2F51otpwbq5a59wu6s70zi.png" alt=" " width="799" height="152"&gt;&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;backend-service:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend health endpoint returns:&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%2Fshmoouv4l2h8tl2cjmi0.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%2Fshmoouv4l2h8tl2cjmi0.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"healthy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"database"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connected"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"redis"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"connected"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The backend is configured with an HPA allowing it to scale between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Minimum replicas: 2
Maximum replicas: 5
CPU target:       50%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  PostgreSQL
&lt;/h2&gt;

&lt;p&gt;PostgreSQL runs as a Kubernetes StatefulSet:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The database uses persistent storage through:&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%2F9ypen17ffmbummjknpgk.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%2F9ypen17ffmbummjknpgk.png" alt=" " width="800" height="419"&gt;&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;StatefulSet
    ↓
PersistentVolumeClaim
    ↓
PersistentVolume
    ↓
AWS EBS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final state was:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;PostgreSQL connectivity was verified using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app postgres-0 &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  pg_isready &lt;span class="nt"&gt;-U&lt;/span&gt; dbadmin &lt;span class="nt"&gt;-d&lt;/span&gt; resilient_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/run/postgresql:5432 - accepting connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Redis
&lt;/h2&gt;

&lt;p&gt;Redis is deployed as a Kubernetes Deployment and exposed internally through:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;redis-service:6379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final cluster state showed Redis healthy with no container restarts.&lt;/p&gt;




&lt;h1&gt;
  
  
  Helm
&lt;/h1&gt;

&lt;p&gt;The application is packaged as a Helm chart:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;manifests/helm/resilient-app/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5vqtfec2oma3x3emu2dv.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%2F5vqtfec2oma3x3emu2dv.png" alt=" " width="799" height="381"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The chart contains:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Chart.yaml
values.yaml
values-dev.yaml
values-staging.yaml
values-prod.yaml
templates/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environment-specific values allow the application configuration to be adapted for different environments without changing the Kubernetes templates.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm template resilient-app &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; values-prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The production configuration was rendered successfully.&lt;/p&gt;




&lt;h1&gt;
  
  
  Helm Validation
&lt;/h1&gt;

&lt;p&gt;The Helm chart was validated before applying changes to the cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  Render the chart
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm template resilient-app &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; values-prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Render to a file
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm template resilient-app &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; values-prod.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rendered-prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Server-side Kubernetes validation
&lt;/h2&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;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server &lt;span class="nt"&gt;-f&lt;/span&gt; rendered-prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This validates the generated resources against the Kubernetes API server without actually applying them.&lt;/p&gt;

&lt;p&gt;This was used throughout the project to catch configuration problems before deployment.&lt;/p&gt;




&lt;h1&gt;
  
  
  Resilience Problems Identified and Resolved
&lt;/h1&gt;

&lt;p&gt;One of the main goals of this project was to troubleshoot real deployment failures rather than simply deploy a predefined manifest.&lt;/p&gt;

&lt;p&gt;Several issues were identified.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Conflicting Kubernetes StorageClass
&lt;/h2&gt;

&lt;p&gt;The Helm chart initially attempted to manage a &lt;code&gt;gp2&lt;/code&gt; StorageClass that already existed in the EKS cluster.&lt;/p&gt;

&lt;p&gt;The cluster already contained:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gp2 (default)
provisioner: kubernetes.io/aws-ebs
type: gp2
fsType: ext4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Helm-generated StorageClass attempted to define conflicting parameters.&lt;/p&gt;

&lt;p&gt;Kubernetes rejected the update because StorageClass parameters are immutable.&lt;/p&gt;

&lt;p&gt;The error was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The StorageClass "gp2" is invalid:
parameters: Forbidden: updates to parameters are forbidden.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Resolution
&lt;/h3&gt;

&lt;p&gt;The application chart was changed so that it does not attempt to recreate or modify the cluster-level StorageClass.&lt;/p&gt;

&lt;p&gt;The application should consume the existing StorageClass rather than treating it as an application-owned resource.&lt;/p&gt;

&lt;p&gt;This follows a cleaner separation of responsibilities:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Cluster infrastructure
        │
        └── StorageClass
                │
                ▼
Application Helm chart
        │
        └──    PVC
                │
                ▼
               EBS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  2. PostgreSQL Persistent Volume Recovery
&lt;/h1&gt;

&lt;p&gt;The most significant resilience exercise involved PostgreSQL persistent storage.&lt;/p&gt;

&lt;p&gt;Initially, Kubernetes reported no PVC/PV resources even though the PostgreSQL pod was still running with an attached EBS-backed filesystem.&lt;/p&gt;

&lt;p&gt;Investigation showed that PostgreSQL was using an EBS volume containing valid PostgreSQL data.&lt;/p&gt;

&lt;p&gt;The underlying volume was identified using the Kubernetes CSI volume information.&lt;/p&gt;

&lt;p&gt;The EBS volume contained the PostgreSQL data directory, including:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PG_VERSION
base/
global/
pg_wal/
pg_xact/
pg_multixact/
pg_logical/
postgresql.conf
pg_hba.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirmed that the database data itself had survived.&lt;/p&gt;




&lt;h1&gt;
  
  
  3. Manual PV Recovery
&lt;/h1&gt;

&lt;p&gt;The original EBS volume was associated with:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;A PersistentVolume was recreated to reference the existing EBS volume through the AWS EBS CSI driver.&lt;/p&gt;

&lt;p&gt;The recovery PV was configured with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;persistentVolumeReclaimPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Retain&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;gp2&lt;/span&gt;
&lt;span class="na"&gt;volumeMode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Filesystem&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The existing EBS volume was referenced through:&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;csi&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ebs.csi.aws.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The recovery PVC was then explicitly bound to the recovered PV.&lt;/p&gt;

&lt;p&gt;After recovery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PVC → Bound
PV  → Bound
PostgreSQL → Running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PostgreSQL data directory was verified after the volume was reattached.&lt;/p&gt;




&lt;h1&gt;
  
  
  4. Persistent Storage Lifecycle Test
&lt;/h1&gt;

&lt;p&gt;A deliberate storage lifecycle test was performed to verify the behavior of the &lt;code&gt;Retain&lt;/code&gt; policy.&lt;/p&gt;

&lt;p&gt;The PostgreSQL StatefulSet was scaled down:&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 statefulset postgres &lt;span class="nt"&gt;--replicas&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PVC was deleted.&lt;/p&gt;

&lt;p&gt;The retained PV entered the &lt;code&gt;Released&lt;/code&gt; state rather than immediately destroying the underlying volume.&lt;/p&gt;

&lt;p&gt;The original EBS volume remained available.&lt;/p&gt;

&lt;p&gt;A new dynamically provisioned volume was observed during the lifecycle test, demonstrating the distinction between:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes PVC lifecycle&lt;/li&gt;
&lt;li&gt;PV lifecycle&lt;/li&gt;
&lt;li&gt;dynamic provisioning&lt;/li&gt;
&lt;li&gt;retained storage&lt;/li&gt;
&lt;li&gt;underlying AWS EBS lifecycle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The original retained PV was then recovered by removing its stale claim reference and binding a recovery PVC to it.&lt;/p&gt;

&lt;p&gt;PostgreSQL was started again and the original database files were confirmed to be present.&lt;/p&gt;

&lt;p&gt;This demonstrated that the data could be recovered independently of the original PVC object.&lt;/p&gt;




&lt;h1&gt;
  
  
  5. PostgreSQL Storage Optimization
&lt;/h1&gt;

&lt;p&gt;After confirming the database data was recovered and PostgreSQL was healthy, the underlying AWS EBS volume was upgraded from:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;The EBS modification was performed using the AWS CLI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;aws ec2 modify-volume &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume-id&lt;/span&gt; &amp;lt;VOLUME_ID&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--volume-type&lt;/span&gt; gp3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--region&lt;/span&gt; us-east-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The volume modification reached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Progress: 100
State: completed
Target type: gp3
Target IOPS: 3000
Target throughput: 125
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL remained operational throughout the storage optimization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Important Kubernetes detail
&lt;/h3&gt;

&lt;p&gt;The underlying AWS EBS volume is now &lt;code&gt;gp3&lt;/code&gt;, while the Kubernetes PV retains its existing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;storageClassName: gp2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is intentional in the recovery context.&lt;/p&gt;

&lt;p&gt;The Kubernetes StorageClass reference is metadata on the PV and is separate from the actual EBS volume type.&lt;/p&gt;

&lt;p&gt;A future production implementation could introduce a dedicated CSI-based &lt;code&gt;gp3&lt;/code&gt; StorageClass and perform a controlled storage migration rather than changing the bound PV metadata directly.&lt;/p&gt;




&lt;h1&gt;
  
  
  6. Backend Container Failure
&lt;/h1&gt;

&lt;p&gt;The backend initially entered:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;The container runtime reported:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;exec: "-listen=:8080": executable file not found in $PATH
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Investigation showed that the Helm chart was using:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;while passing arguments intended for:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;hashicorp/http-echo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application manifest expected:&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;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/http-echo:latest&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Helm values were corrected accordingly.&lt;/p&gt;

&lt;p&gt;After the change, the backend pods became healthy:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;and the Deployment successfully reached its desired replica count.&lt;/p&gt;




&lt;h1&gt;
  
  
  7. PostgreSQL Health Probe Failure
&lt;/h1&gt;

&lt;p&gt;PostgreSQL initially generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FATAL: database "dbadmin" does not exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The investigation revealed an important distinction:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;was the PostgreSQL &lt;strong&gt;role/user&lt;/strong&gt;, while:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;was the actual application database.&lt;/p&gt;

&lt;p&gt;The original probes used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_isready &lt;span class="nt"&gt;-U&lt;/span&gt; dbadmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;PostgreSQL interpreted the connection target as the database named after the user:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;which did not exist.&lt;/p&gt;

&lt;p&gt;The probes were corrected to:&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;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pg_isready&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-U&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;dbadmin&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;-d&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;resilient_db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both liveness and readiness probes were updated.&lt;/p&gt;

&lt;p&gt;After deployment, PostgreSQL logs showed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;database system is ready to accept connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pg_isready &lt;span class="nt"&gt;-U&lt;/span&gt; dbadmin &lt;span class="nt"&gt;-d&lt;/span&gt; resilient_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/run/postgresql:5432 - accepting connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Horizontal Pod Autoscaling
&lt;/h1&gt;

&lt;p&gt;The backend uses a Horizontal Pod Autoscaler.&lt;/p&gt;

&lt;p&gt;Configuration:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fygchsp3s4285wjlv1kwo.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%2Fygchsp3s4285wjlv1kwo.png" alt=" " width="800" height="419"&gt;&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;Minimum replicas: 2
Maximum replicas: 5
CPU target:       50%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HPA was verified in the cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get hpa &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final observed state:&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%2F1ey8v63o4yr8nkjtylmv.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%2F1ey8v63o4yr8nkjtylmv.png" alt=" " width="799" height="315"&gt;&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;TARGETS      MINPODS   MAXPODS   REPLICAS
cpu: 2%/50%  2         5         2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HPA behavior was also observed through Kubernetes events.&lt;/p&gt;

&lt;p&gt;During testing, the backend scaled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 → 3 replicas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and later automatically scaled back:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;3 → 2 replicas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with Kubernetes reporting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reason: All metrics below target
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This confirms that the HPA was actively managing the backend workload rather than merely existing as an unused resource.&lt;/p&gt;




&lt;h1&gt;
  
  
  Application Networking
&lt;/h1&gt;

&lt;p&gt;The application uses Kubernetes Services for internal communication.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend-service
    ClusterIP
    port 8080

frontend-service
    ClusterIP
    port 80

postgres-service
    Headless Service
    port 5432

redis-service
    Headless Service
    port 6379
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PostgreSQL and Redis services use headless service configurations to support stateful/internal service discovery.&lt;/p&gt;




&lt;h1&gt;
  
  
  AWS Application Load Balancer
&lt;/h1&gt;

&lt;p&gt;The application is exposed through a Kubernetes Ingress using the AWS Load Balancer Controller.&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%2Fl0jcfeys35yig03jq777.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%2Fl0jcfeys35yig03jq777.png" alt=" " width="799" height="269"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resilient-app-ingress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;The EKS cluster successfully provisioned an AWS Application Load Balancer.&lt;/p&gt;

&lt;p&gt;The final cluster state showed an ALB DNS address associated with the Ingress.&lt;/p&gt;




&lt;h1&gt;
  
  
  Validation and Testing
&lt;/h1&gt;

&lt;p&gt;The final cluster was validated using several Kubernetes commands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pod health
&lt;/h2&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; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final observed state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend      Running
frontend     Running
postgres     Running
redis        Running
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All observed pods had:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h2&gt;
  
  
  Deployment health
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get statefulset,deployment &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flfgbkx162xksv2r8thbj.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%2Flfgbkx162xksv2r8thbj.png" alt=" " width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Final state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;postgres    1/1
backend     2/2
frontend    2/2
redis       1/1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Persistent storage
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pvc,pv &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Final PostgreSQL storage:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;PVC:           Bound
PV:            Bound
Access Mode:   RWO
Capacity:      1Gi
Reclaim Policy: Retain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Services and Ingress
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc,ingress &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;backend-service
frontend-service
postgres-service
redis-service
resilient-app-ingress
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  HPA
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get hpa &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MINPODS: 2
MAXPODS: 5
CPU TARGET: 50%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  PostgreSQL health
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app postgres-0 &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  pg_isready &lt;span class="nt"&gt;-U&lt;/span&gt; dbadmin &lt;span class="nt"&gt;-d&lt;/span&gt; resilient_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/var/run/postgresql:5432 - accepting connections
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Frontend application test
&lt;/h2&gt;

&lt;p&gt;The frontend service was tested using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/frontend-service 8091:80 &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application returned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Kubernetes Resilience Demo - Frontend Active
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Backend application test
&lt;/h2&gt;

&lt;p&gt;The backend service was tested using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward svc/backend-service 8092:8080 &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The service successfully accepted connections through the port-forward.&lt;/p&gt;




&lt;h1&gt;
  
  
  Final Cluster State
&lt;/h1&gt;

&lt;p&gt;The final observed application state was:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Namespace: resilient-app

Backend:
  2/2 Running

Frontend:
  2/2 Running

PostgreSQL:
  1/1 Running

Redis:
  1/1 Running

PostgreSQL PVC:
  Bound

PostgreSQL PV:
  Bound

PV Reclaim Policy:
  Retain

Backend HPA:
  2–5 replicas
  50% CPU target

Ingress:
  AWS ALB

Pod Restarts:
  0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Key Resilience Principles Demonstrated
&lt;/h1&gt;

&lt;p&gt;This project demonstrates several important Kubernetes and platform engineering principles.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Stateful workloads require deliberate storage management
&lt;/h3&gt;

&lt;p&gt;A PostgreSQL pod can be recreated, but database data must live independently from the pod lifecycle.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pod lifecycle ≠ Data lifecycle
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  2. PVCs, PVs, StorageClasses, and cloud volumes are different layers
&lt;/h3&gt;

&lt;p&gt;The project demonstrates the relationship between:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
    ↓
StatefulSet
    ↓
PVC
    ↓
PV
    ↓
CSI Driver
    ↓
AWS EBS
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Understanding these layers was essential to recovering the database.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Retain policies provide an additional recovery boundary
&lt;/h3&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;persistentVolumeReclaimPolicy: Retain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;helps prevent automatic destruction of the underlying persistent volume when a claim is removed.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Health checks must test the real application dependency
&lt;/h3&gt;

&lt;p&gt;A syntactically valid probe can still be logically wrong.&lt;/p&gt;

&lt;p&gt;The PostgreSQL probe initially checked the wrong database.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pg_isready -U dbadmin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pg_isready -U dbadmin -d resilient_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;aligned the health check with the actual application database.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Autoscaling should be tested, not assumed
&lt;/h3&gt;

&lt;p&gt;The HPA was observed scaling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2 → 3 → 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;based on CPU utilization.&lt;/p&gt;

&lt;p&gt;This provided evidence that the autoscaling configuration was functioning.&lt;/p&gt;




&lt;h3&gt;
  
  
  6. Helm should consume infrastructure rather than unnecessarily own it
&lt;/h3&gt;

&lt;p&gt;Cluster-level resources such as StorageClasses should be managed deliberately.&lt;/p&gt;

&lt;p&gt;The application chart should consume an appropriate existing StorageClass instead of attempting to redefine a cluster-wide resource with the same name.&lt;/p&gt;




&lt;h1&gt;
  
  
  Security Considerations
&lt;/h1&gt;

&lt;p&gt;Sensitive configuration should &lt;strong&gt;not&lt;/strong&gt; be committed to source control.&lt;/p&gt;

&lt;p&gt;The repository should use:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes Secrets&lt;/li&gt;
&lt;li&gt;External Secrets where appropriate&lt;/li&gt;
&lt;li&gt;AWS Secrets Manager or Parameter Store for production environments&lt;/li&gt;
&lt;li&gt;Environment-specific configuration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.gitignore&lt;/code&gt; for local secrets&lt;/li&gt;
&lt;li&gt;Secret scanning before pushing to GitHub&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example production architecture:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Application
     │
     ▼
Kubernetes Secret
     │
     ▲
External secret management
     │
     ▼
AWS Secrets Manager
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Never commit real passwords, AWS credentials, tokens, kubeconfig files, or private keys to this repository.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  Operational Lessons
&lt;/h1&gt;

&lt;p&gt;This project provided hands-on experience with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kubernetes troubleshooting&lt;/li&gt;
&lt;li&gt;EKS operations&lt;/li&gt;
&lt;li&gt;StatefulSets&lt;/li&gt;
&lt;li&gt;PersistentVolumes&lt;/li&gt;
&lt;li&gt;PersistentVolumeClaims&lt;/li&gt;
&lt;li&gt;StorageClasses&lt;/li&gt;
&lt;li&gt;AWS EBS&lt;/li&gt;
&lt;li&gt;AWS EBS CSI&lt;/li&gt;
&lt;li&gt;Kubernetes scheduling&lt;/li&gt;
&lt;li&gt;Kubernetes events&lt;/li&gt;
&lt;li&gt;Health probes&lt;/li&gt;
&lt;li&gt;Container entrypoints&lt;/li&gt;
&lt;li&gt;Helm templating&lt;/li&gt;
&lt;li&gt;Environment-specific values&lt;/li&gt;
&lt;li&gt;Kubernetes Services&lt;/li&gt;
&lt;li&gt;AWS ALB Ingress&lt;/li&gt;
&lt;li&gt;HPA&lt;/li&gt;
&lt;li&gt;Storage recovery&lt;/li&gt;
&lt;li&gt;Data persistence&lt;/li&gt;
&lt;li&gt;Failure investigation&lt;/li&gt;
&lt;li&gt;Production-style validation&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Future Improvements
&lt;/h1&gt;

&lt;p&gt;This project is intentionally structured as a foundation for a larger Platform Engineering capstone.&lt;/p&gt;

&lt;p&gt;Planned improvements include:&lt;/p&gt;

&lt;h3&gt;
  
  
  Infrastructure as Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Terraform for EKS&lt;/li&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;IAM&lt;/li&gt;
&lt;li&gt;Security Groups&lt;/li&gt;
&lt;li&gt;EBS CSI&lt;/li&gt;
&lt;li&gt;AWS Load Balancer Controller&lt;/li&gt;
&lt;li&gt;Supporting AWS infrastructure&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  CI/CD
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;Helm linting&lt;/li&gt;
&lt;li&gt;YAML validation&lt;/li&gt;
&lt;li&gt;Security scanning&lt;/li&gt;
&lt;li&gt;Container image scanning&lt;/li&gt;
&lt;li&gt;Automated deployment&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Argo CD&lt;/li&gt;
&lt;li&gt;Declarative application delivery&lt;/li&gt;
&lt;li&gt;Environment promotion&lt;/li&gt;
&lt;li&gt;Automated drift detection&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Observability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Prometheus&lt;/li&gt;
&lt;li&gt;Grafana&lt;/li&gt;
&lt;li&gt;Alertmanager&lt;/li&gt;
&lt;li&gt;Application metrics&lt;/li&gt;
&lt;li&gt;Kubernetes metrics&lt;/li&gt;
&lt;li&gt;SLO/SLI monitoring&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;NetworkPolicies&lt;/li&gt;
&lt;li&gt;Pod Security Standards&lt;/li&gt;
&lt;li&gt;Non-root containers&lt;/li&gt;
&lt;li&gt;Read-only root filesystems where applicable&lt;/li&gt;
&lt;li&gt;RBAC hardening&lt;/li&gt;
&lt;li&gt;Image scanning&lt;/li&gt;
&lt;li&gt;Secrets management&lt;/li&gt;
&lt;li&gt;AWS IAM least privilege&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reliability
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;PodDisruptionBudgets&lt;/li&gt;
&lt;li&gt;Pod anti-affinity&lt;/li&gt;
&lt;li&gt;Multi-AZ scheduling&lt;/li&gt;
&lt;li&gt;Automated backup and restore&lt;/li&gt;
&lt;li&gt;PostgreSQL backup strategy&lt;/li&gt;
&lt;li&gt;Disaster recovery testing&lt;/li&gt;
&lt;li&gt;Recovery Time Objective (RTO)&lt;/li&gt;
&lt;li&gt;Recovery Point Objective (RPO)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Platform Engineering
&lt;/h3&gt;

&lt;p&gt;The long-term goal is to evolve this project into a reusable internal platform that allows application teams to deploy workloads through standardized:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Infrastructure
    ↓
Kubernetes
    ↓
Helm
    ↓
CI/CD
    ↓
GitOps
    ↓
Observability
    ↓
Security
    ↓
Reliability
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Useful Commands
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Helm
&lt;/h2&gt;



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

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm template resilient-app &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; values-prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm template resilient-app &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; values-prod.yaml &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; rendered-prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Kubernetes validation
&lt;/h2&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;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;server &lt;span class="nt"&gt;-f&lt;/span&gt; rendered-prod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get svc &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get pvc,pv &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get statefulset,deployment &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get hpa &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get ingress &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get events &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app &lt;span class="nt"&gt;--sort-by&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'.lastTimestamp'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  PostgreSQL
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app postgres-0 &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  pg_isready &lt;span class="nt"&gt;-U&lt;/span&gt; dbadmin &lt;span class="nt"&gt;-d&lt;/span&gt; resilient_db
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Service testing
&lt;/h2&gt;

&lt;p&gt;Frontend:&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/frontend-service 8091:80 &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Backend:&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/backend-service 8092:8080 &lt;span class="nt"&gt;-n&lt;/span&gt; resilient-app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  Project Outcome
&lt;/h1&gt;

&lt;p&gt;The project successfully demonstrated a resilient Kubernetes application running on Amazon EKS.&lt;/p&gt;

&lt;p&gt;The final implementation successfully achieved:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Helm-based application packaging&lt;/li&gt;
&lt;li&gt;Multi-component Kubernetes deployment&lt;/li&gt;
&lt;li&gt;Persistent PostgreSQL storage&lt;/li&gt;
&lt;li&gt;PostgreSQL PV/PVC recovery&lt;/li&gt;
&lt;li&gt;Persistent volume retention testing&lt;/li&gt;
&lt;li&gt;AWS EBS volume recovery&lt;/li&gt;
&lt;li&gt;AWS EBS gp2 → gp3 optimization&lt;/li&gt;
&lt;li&gt;Correct PostgreSQL health probes&lt;/li&gt;
&lt;li&gt;Backend container failure resolution&lt;/li&gt;
&lt;li&gt;Kubernetes Service validation&lt;/li&gt;
&lt;li&gt;AWS ALB Ingress provisioning&lt;/li&gt;
&lt;li&gt;Horizontal Pod Autoscaling&lt;/li&gt;
&lt;li&gt;Application health validation&lt;/li&gt;
&lt;li&gt;Zero pod restarts in the final observed state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most importantly, the project demonstrated the ability to &lt;strong&gt;troubleshoot and recover a stateful Kubernetes workload instead of simply redeploying it and losing the underlying data&lt;/strong&gt;.&lt;/p&gt;




&lt;h1&gt;
  
  
  Author
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Jamiu Bakre&lt;/strong&gt;&lt;br&gt;
Cloud &amp;amp; DevOps Engineer | Kubernetes | AWS | Terraform | CI/CD&lt;/p&gt;

&lt;p&gt;Focus areas:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cloud Engineering&lt;/li&gt;
&lt;li&gt;DevOps&lt;/li&gt;
&lt;li&gt;Kubernetes&lt;/li&gt;
&lt;li&gt;Platform Engineering&lt;/li&gt;
&lt;li&gt;Infrastructure as Code&lt;/li&gt;
&lt;li&gt;AWS&lt;/li&gt;
&lt;li&gt;CI/CD&lt;/li&gt;
&lt;li&gt;Reliability Engineering&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Status
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Assignment status: Completed — Design &amp;amp; Deploy a Resilient Kubernetes Application&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This repository represents the first stage of a broader Platform Engineering journey and is intended to evolve into a more complete production-style platform with Infrastructure as Code, GitOps, observability, security automation, and automated reliability testing.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>postgres</category>
      <category>redis</category>
    </item>
    <item>
      <title>Multi-Environment Terraform Infrastructure on AWS</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Wed, 16 Sep 2026 17:40:39 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/multi-environment-terraform-infrastructure-on-aws-1ko6</link>
      <guid>https://dev.to/jamiu_cloud/multi-environment-terraform-infrastructure-on-aws-1ko6</guid>
      <description>&lt;h2&gt;
  
  
  📌 Project Overview
&lt;/h2&gt;

&lt;p&gt;This project demonstrates the design and deployment of AWS infrastructure across three independent environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Development&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Staging&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The infrastructure is built using reusable Terraform modules rather than duplicating infrastructure code for every environment.&lt;/p&gt;

&lt;p&gt;Each environment consumes the same core modules while using environment-specific configuration for infrastructure sizing, scaling, networking, and database requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Objectives
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Build reusable Terraform modules&lt;/li&gt;
&lt;li&gt;Separate development, staging, and production environments&lt;/li&gt;
&lt;li&gt;Implement AWS networking infrastructure&lt;/li&gt;
&lt;li&gt;Deploy scalable compute infrastructure&lt;/li&gt;
&lt;li&gt;Deploy private RDS PostgreSQL databases&lt;/li&gt;
&lt;li&gt;Configure Terraform remote state&lt;/li&gt;
&lt;li&gt;Implement state locking&lt;/li&gt;
&lt;li&gt;Apply Infrastructure as Code best practices&lt;/li&gt;
&lt;li&gt;Maintain a clean and reproducible Git workflow&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🏗️ Architecture
&lt;/h1&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%2F7y0k3sua30dl66gk43ix.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7y0k3sua30dl66gk43ix.jpg" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  📁 Repository Structure
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform-Project/
│
├── .gitignore
├── .terraform.lock.hcl
│
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── nacl.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   │
│   ├── compute/
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   │
│   └── database/
│       ├── main.tf
│       ├── outputs.tf
│       └── variables.tf
│
├── dev/
│   ├── .terraform.lock.hcl
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   ├── variables.tf
│   └── versions.tf
│
├── stage/
│   ├── .terraform.lock.hcl
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   ├── variables.tf
│   └── versions.tf
│
└── prod/
    ├── .terraform.lock.hcl
    ├── main.tf
    ├── outputs.tf
    ├── provider.tf
    ├── variables.tf
    └── versions.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔐 Files intentionally excluded from Git
&lt;/h3&gt;

&lt;p&gt;Environment-specific files such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform.tfvars
backend.hcl
terraform.tfstate
tfplan
.terraform/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are excluded from the repository through &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This prevents environment-specific configuration, state files, generated plans, and potentially sensitive values from being committed.&lt;/p&gt;




&lt;h1&gt;
  
  
  🧩 Terraform Modules
&lt;/h1&gt;

&lt;h2&gt;
  
  
  🌐 VPC Module
&lt;/h2&gt;

&lt;p&gt;The networking module provisions the foundational AWS network infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Public subnets&lt;/li&gt;
&lt;li&gt;Private subnets&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;NAT Gateways&lt;/li&gt;
&lt;li&gt;Route tables&lt;/li&gt;
&lt;li&gt;Route table associations&lt;/li&gt;
&lt;li&gt;Network ACL configuration&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🖥️ Compute Module
&lt;/h2&gt;

&lt;p&gt;The compute module provides scalable application infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;EC2 Launch Template&lt;/li&gt;
&lt;li&gt;Auto Scaling Group&lt;/li&gt;
&lt;li&gt;Application Security Group&lt;/li&gt;
&lt;li&gt;Environment-specific scaling configuration&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🗄️ Database Module
&lt;/h2&gt;

&lt;p&gt;The database module provisions PostgreSQL infrastructure using Amazon RDS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;RDS PostgreSQL&lt;/li&gt;
&lt;li&gt;DB subnet group&lt;/li&gt;
&lt;li&gt;Database Security Group&lt;/li&gt;
&lt;li&gt;Private database networking&lt;/li&gt;
&lt;li&gt;Encryption configuration&lt;/li&gt;
&lt;li&gt;Backup configuration&lt;/li&gt;
&lt;li&gt;Production Multi-AZ configuration&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🌎 Environment Design
&lt;/h1&gt;

&lt;p&gt;Each environment uses the same reusable modules while allowing infrastructure capacity to vary according to its purpose.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;VPC CIDR&lt;/th&gt;
&lt;th&gt;EC2 Type&lt;/th&gt;
&lt;th&gt;ASG Min&lt;/th&gt;
&lt;th&gt;ASG Desired&lt;/th&gt;
&lt;th&gt;ASG Max&lt;/th&gt;
&lt;th&gt;RDS&lt;/th&gt;
&lt;th&gt;Multi-AZ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Development&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t3.micro&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.1.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t3.small&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.2.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t3.medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;PostgreSQL&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;Production is intentionally configured with higher capacity and resilience than development and staging.&lt;/p&gt;




&lt;h1&gt;
  
  
  ☁️ AWS Services
&lt;/h1&gt;

&lt;p&gt;This project uses several AWS services:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWS Service&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;Amazon VPC&lt;/td&gt;
&lt;td&gt;Network isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subnets&lt;/td&gt;
&lt;td&gt;Public/private network segmentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internet Gateway&lt;/td&gt;
&lt;td&gt;Internet connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateway&lt;/td&gt;
&lt;td&gt;Outbound private subnet connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route Tables&lt;/td&gt;
&lt;td&gt;Network traffic routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network ACLs&lt;/td&gt;
&lt;td&gt;Subnet-level traffic control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EC2&lt;/td&gt;
&lt;td&gt;Application compute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto Scaling&lt;/td&gt;
&lt;td&gt;Application scalability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch Template&lt;/td&gt;
&lt;td&gt;EC2 configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security Groups&lt;/td&gt;
&lt;td&gt;Instance-level network security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon RDS&lt;/td&gt;
&lt;td&gt;Managed PostgreSQL database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;td&gt;Terraform remote state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DynamoDB&lt;/td&gt;
&lt;td&gt;Terraform state locking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53t1zzzrjd5lbcn3umwr.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%2F53t1zzzrjd5lbcn3umwr.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  🔐 Terraform Remote State
&lt;/h1&gt;

&lt;p&gt;Terraform state is stored remotely using Amazon S3.&lt;/p&gt;

&lt;p&gt;State locking is implemented using DynamoDB.&lt;/p&gt;

&lt;p&gt;Example backend 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;bucket&lt;/span&gt;         &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"multi-environment-terraform-150926"&lt;/span&gt;
&lt;span class="nx"&gt;key&lt;/span&gt;            &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev/terraform.tfstate"&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt;         &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
&lt;span class="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-locks"&lt;/span&gt;
&lt;span class="nx"&gt;encrypt&lt;/span&gt;        &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each environment uses its own state key to maintain environment isolation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The actual backend configuration files are intentionally excluded from this repository.&lt;/p&gt;
&lt;/blockquote&gt;




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

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

&lt;p&gt;Install and configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;AWS CLI&lt;/li&gt;
&lt;li&gt;AWS credentials&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your AWS identity must have appropriate permissions to provision the required infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Clone the Repository
&lt;/h2&gt;

&lt;p&gt;Using SSH:&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 git@github.com:Jahmeeu-Cloud/terraform-aws-multi-environment-infrastructure.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&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;terraform-aws-multi-environment-infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔧 Deploy Development
&lt;/h1&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;dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize Terraform:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;-recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;Create a plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfvars &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;View outputs:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔧 Deploy Staging
&lt;/h1&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; ../stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfvars &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔧 Deploy Production
&lt;/h1&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; ../prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfvars &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔄 Standard Terraform Workflow
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform &lt;span class="nb"&gt;fmt
&lt;/span&gt;terraform validate
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a controlled deployment using a saved plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
terraform apply tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect deployed outputs:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  🛡️ Security Considerations
&lt;/h1&gt;

&lt;p&gt;This project is designed as a practical cloud engineering and Infrastructure-as-Code portfolio project.&lt;/p&gt;

&lt;p&gt;Several production hardening improvements should be considered before using the configuration for a real production workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current Security Design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;RDS is deployed in private subnets.&lt;/li&gt;
&lt;li&gt;Database access is controlled through security groups.&lt;/li&gt;
&lt;li&gt;Production RDS is encrypted.&lt;/li&gt;
&lt;li&gt;Production RDS uses Multi-AZ.&lt;/li&gt;
&lt;li&gt;Production RDS has deletion protection enabled.&lt;/li&gt;
&lt;li&gt;Terraform state is stored remotely.&lt;/li&gt;
&lt;li&gt;Environment-specific configuration files are excluded from Git.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Recommended Improvements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Replace direct SSH access with AWS Systems Manager Session Manager.&lt;/li&gt;
&lt;li&gt;Restrict SSH source IPs where SSH is required.&lt;/li&gt;
&lt;li&gt;Store database credentials in AWS Secrets Manager or Systems Manager Parameter Store.&lt;/li&gt;
&lt;li&gt;Place application instances in private subnets.&lt;/li&gt;
&lt;li&gt;Introduce an Application Load Balancer.&lt;/li&gt;
&lt;li&gt;Add CloudWatch monitoring and alarms.&lt;/li&gt;
&lt;li&gt;Implement least-privilege IAM policies.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  💰 Cost Considerations
&lt;/h1&gt;

&lt;p&gt;The infrastructure contains AWS resources that can generate ongoing costs, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NAT Gateways&lt;/li&gt;
&lt;li&gt;EC2 instances&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;li&gt;RDS&lt;/li&gt;
&lt;li&gt;Elastic IPs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For development and testing, unused environments should be destroyed when no longer required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfvars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Always verify the Terraform plan before destroying infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  🧠 Skills Demonstrated
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Infrastructure as Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Terraform Modules&lt;/li&gt;
&lt;li&gt;Remote State&lt;/li&gt;
&lt;li&gt;State Locking&lt;/li&gt;
&lt;li&gt;Environment Isolation&lt;/li&gt;
&lt;li&gt;Infrastructure Automation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AWS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;Auto Scaling&lt;/li&gt;
&lt;li&gt;RDS&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;DynamoDB&lt;/li&gt;
&lt;li&gt;IAM&lt;/li&gt;
&lt;li&gt;Security Groups&lt;/li&gt;
&lt;li&gt;Network ACLs&lt;/li&gt;
&lt;li&gt;NAT Gateway&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;Route Tables&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DevOps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;Infrastructure automation&lt;/li&gt;
&lt;li&gt;Environment management&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Modular infrastructure&lt;/li&gt;
&lt;li&gt;Public/private network segmentation&lt;/li&gt;
&lt;li&gt;High availability concepts&lt;/li&gt;
&lt;li&gt;Scalable compute&lt;/li&gt;
&lt;li&gt;Managed database architecture&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  📈 Future Improvements
&lt;/h1&gt;

&lt;p&gt;Planned improvements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Application Load Balancer&lt;/li&gt;
&lt;li&gt;[ ] Private application subnets&lt;/li&gt;
&lt;li&gt;[ ] AWS Systems Manager integration&lt;/li&gt;
&lt;li&gt;[ ] CloudWatch dashboards and alarms&lt;/li&gt;
&lt;li&gt;[ ] GitHub Actions CI/CD&lt;/li&gt;
&lt;li&gt;[ ] Terraform plan checks on pull requests&lt;/li&gt;
&lt;li&gt;[ ] Infracost integration&lt;/li&gt;
&lt;li&gt;[ ] Secrets Manager integration&lt;/li&gt;
&lt;li&gt;[ ] Additional security hardening&lt;/li&gt;
&lt;li&gt;[ ] Cost optimization for NAT Gateways&lt;/li&gt;
&lt;li&gt;[ ] Automated infrastructure testing&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🎯 Project Purpose
&lt;/h1&gt;

&lt;p&gt;This project was built to demonstrate practical experience designing and deploying cloud infrastructure using &lt;strong&gt;Terraform and AWS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rather than maintaining separate duplicated Terraform configurations, reusable modules are used across multiple environments.&lt;/p&gt;

&lt;p&gt;The project demonstrates how infrastructure can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reusable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment-aware&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version controlled&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easier to maintain&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It forms part of my ongoing Cloud, DevOps, and Platform Engineering learning journey.&lt;/p&gt;




&lt;h1&gt;
  
  
  👨🏾‍💻 Author
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Jamiu Olatunji Bakre
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cloud / DevOps Engineer | AWS | Terraform | Kubernetes | Platform Engineering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I build and automate cloud infrastructure with a focus on AWS, Infrastructure as Code, DevOps practices, and scalable platform engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect With Me
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Jahmeeu-Cloud" rel="noopener noreferrer"&gt;Jahmeeu-Cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://linkedin.com/in/jamiu-bakre/" rel="noopener noreferrer"&gt;Jamiu Bakre&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://jahmeeu-cloud.github.io/portfolio-project/" rel="noopener noreferrer"&gt;CloudWithHorla Portfolio&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⭐ If you find this project useful
&lt;/h2&gt;

&lt;p&gt;Feel free to explore the repository, review the Terraform modules, and follow the project as it evolves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built with Terraform + AWS ☁️&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>platformengineer</category>
      <category>github</category>
    </item>
    <item>
      <title>Multi-Environment Terraform Infrastructure on AWS</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Tue, 15 Sep 2026 16:34:38 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/multi-environment-terraform-infrastructure-on-aws-41fp</link>
      <guid>https://dev.to/jamiu_cloud/multi-environment-terraform-infrastructure-on-aws-41fp</guid>
      <description>&lt;h2&gt;
  
  
  📌 Project Overview
&lt;/h2&gt;

&lt;p&gt;This project demonstrates the design and deployment of AWS infrastructure across three independent environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Development&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Staging&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Production&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The infrastructure is built using reusable Terraform modules rather than duplicating infrastructure code for every environment.&lt;/p&gt;

&lt;p&gt;Each environment consumes the same core modules while using environment-specific configuration for infrastructure sizing, scaling, networking, and database requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Objectives
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Build reusable Terraform modules&lt;/li&gt;
&lt;li&gt;Separate development, staging, and production environments&lt;/li&gt;
&lt;li&gt;Implement AWS networking infrastructure&lt;/li&gt;
&lt;li&gt;Deploy scalable compute infrastructure&lt;/li&gt;
&lt;li&gt;Deploy private RDS PostgreSQL databases&lt;/li&gt;
&lt;li&gt;Configure Terraform remote state&lt;/li&gt;
&lt;li&gt;Implement state locking&lt;/li&gt;
&lt;li&gt;Apply Infrastructure as Code best practices&lt;/li&gt;
&lt;li&gt;Maintain a clean and reproducible Git workflow&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🏗️ Architecture
&lt;/h1&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%2F1czo8pcq8z5qxjyqfo9b.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%2F1czo8pcq8z5qxjyqfo9b.png" alt=" " width="772" height="951"&gt;&lt;/a&gt;&lt;/p&gt;




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




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhshmfnrfkhillbrq3pf2.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%2Fhshmfnrfkhillbrq3pf2.png" alt=" " width="800" height="840"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  📁 Repository Structure
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform-Project/
│
├── .gitignore
├── .terraform.lock.hcl
│
├── modules/
│   ├── vpc/
│   │   ├── main.tf
│   │   ├── nacl.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   │
│   ├── compute/
│   │   ├── main.tf
│   │   ├── outputs.tf
│   │   └── variables.tf
│   │
│   └── database/
│       ├── main.tf
│       ├── outputs.tf
│       └── variables.tf
│
├── dev/
│   ├── .terraform.lock.hcl
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   ├── variables.tf
│   └── versions.tf
│
├── stage/
│   ├── .terraform.lock.hcl
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   ├── variables.tf
│   └── versions.tf
│
└── prod/
    ├── .terraform.lock.hcl
    ├── main.tf
    ├── outputs.tf
    ├── provider.tf
    ├── variables.tf
    └── versions.tf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  🔐 Files intentionally excluded from Git
&lt;/h3&gt;

&lt;p&gt;Environment-specific files such as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform.tfvars
backend.hcl
terraform.tfstate
tfplan
.terraform/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;are excluded from the repository through &lt;code&gt;.gitignore&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This prevents environment-specific configuration, state files, generated plans, and potentially sensitive values from being committed.&lt;/p&gt;




&lt;h1&gt;
  
  
  🧩 Terraform Modules
&lt;/h1&gt;

&lt;h2&gt;
  
  
  🌐 VPC Module
&lt;/h2&gt;

&lt;p&gt;The networking module provisions the foundational AWS network infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;Public subnets&lt;/li&gt;
&lt;li&gt;Private subnets&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;NAT Gateways&lt;/li&gt;
&lt;li&gt;Route tables&lt;/li&gt;
&lt;li&gt;Route table associations&lt;/li&gt;
&lt;li&gt;Network ACL configuration&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🖥️ Compute Module
&lt;/h2&gt;

&lt;p&gt;The compute module provides scalable application infrastructure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;EC2 Launch Template&lt;/li&gt;
&lt;li&gt;Auto Scaling Group&lt;/li&gt;
&lt;li&gt;Application Security Group&lt;/li&gt;
&lt;li&gt;Environment-specific scaling configuration&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🗄️ Database Module
&lt;/h2&gt;

&lt;p&gt;The database module provisions PostgreSQL infrastructure using Amazon RDS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Components
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;RDS PostgreSQL&lt;/li&gt;
&lt;li&gt;DB subnet group&lt;/li&gt;
&lt;li&gt;Database Security Group&lt;/li&gt;
&lt;li&gt;Private database networking&lt;/li&gt;
&lt;li&gt;Encryption configuration&lt;/li&gt;
&lt;li&gt;Backup configuration&lt;/li&gt;
&lt;li&gt;Production Multi-AZ configuration&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🌎 Environment Design
&lt;/h1&gt;

&lt;p&gt;Each environment uses the same reusable modules while allowing infrastructure capacity to vary according to its purpose.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Environment&lt;/th&gt;
&lt;th&gt;VPC CIDR&lt;/th&gt;
&lt;th&gt;EC2 Type&lt;/th&gt;
&lt;th&gt;ASG Min&lt;/th&gt;
&lt;th&gt;ASG Desired&lt;/th&gt;
&lt;th&gt;ASG Max&lt;/th&gt;
&lt;th&gt;RDS&lt;/th&gt;
&lt;th&gt;Multi-AZ&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Development&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.0.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t3.micro&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Staging&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.1.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t3.small&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production&lt;/td&gt;
&lt;td&gt;&lt;code&gt;10.2.0.0/16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;t3.medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;PostgreSQL&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;Production is intentionally configured with higher capacity and resilience than development and staging.&lt;/p&gt;




&lt;h1&gt;
  
  
  ☁️ AWS Services
&lt;/h1&gt;

&lt;p&gt;This project uses several AWS services:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;AWS Service&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;Amazon VPC&lt;/td&gt;
&lt;td&gt;Network isolation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subnets&lt;/td&gt;
&lt;td&gt;Public/private network segmentation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Internet Gateway&lt;/td&gt;
&lt;td&gt;Internet connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateway&lt;/td&gt;
&lt;td&gt;Outbound private subnet connectivity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route Tables&lt;/td&gt;
&lt;td&gt;Network traffic routing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Network ACLs&lt;/td&gt;
&lt;td&gt;Subnet-level traffic control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EC2&lt;/td&gt;
&lt;td&gt;Application compute&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto Scaling&lt;/td&gt;
&lt;td&gt;Application scalability&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Launch Template&lt;/td&gt;
&lt;td&gt;EC2 configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Security Groups&lt;/td&gt;
&lt;td&gt;Instance-level network security&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon RDS&lt;/td&gt;
&lt;td&gt;Managed PostgreSQL database&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;td&gt;Terraform remote state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DynamoDB&lt;/td&gt;
&lt;td&gt;Terraform state locking&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F53t1zzzrjd5lbcn3umwr.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%2F53t1zzzrjd5lbcn3umwr.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  🔐 Terraform Remote State
&lt;/h1&gt;

&lt;p&gt;Terraform state is stored remotely using Amazon S3.&lt;/p&gt;

&lt;p&gt;State locking is implemented using DynamoDB.&lt;/p&gt;

&lt;p&gt;Example backend 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;bucket&lt;/span&gt;         &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"multi-environment-terraform-150926"&lt;/span&gt;
&lt;span class="nx"&gt;key&lt;/span&gt;            &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"dev/terraform.tfstate"&lt;/span&gt;
&lt;span class="nx"&gt;region&lt;/span&gt;         &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"eu-west-1"&lt;/span&gt;
&lt;span class="nx"&gt;dynamodb_table&lt;/span&gt; &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform-locks"&lt;/span&gt;
&lt;span class="nx"&gt;encrypt&lt;/span&gt;        &lt;span class="err"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each environment uses its own state key to maintain environment isolation.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The actual backend configuration files are intentionally excluded from this repository.&lt;/p&gt;
&lt;/blockquote&gt;




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

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

&lt;p&gt;Install and configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;AWS CLI&lt;/li&gt;
&lt;li&gt;AWS credentials&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Your AWS identity must have appropriate permissions to provision the required infrastructure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Clone the Repository
&lt;/h2&gt;

&lt;p&gt;Using SSH:&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 git@github.com:Jahmeeu-Cloud/terraform-aws-multi-environment-infrastructure.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then:&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;terraform-aws-multi-environment-infrastructure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔧 Deploy Development
&lt;/h1&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;dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Initialize Terraform:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform &lt;span class="nb"&gt;fmt&lt;/span&gt; &lt;span class="nt"&gt;-recursive&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;Create a plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfvars &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



&lt;p&gt;View outputs:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔧 Deploy Staging
&lt;/h1&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; ../stage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfvars &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔧 Deploy Production
&lt;/h1&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; ../prod
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;



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

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

&lt;/div&gt;



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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfvars &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔄 Standard Terraform Workflow
&lt;/h1&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform &lt;span class="nb"&gt;fmt
&lt;/span&gt;terraform validate
terraform plan
terraform apply
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a controlled deployment using a saved plan:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform plan &lt;span class="nt"&gt;-out&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tfplan
terraform apply tfplan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To inspect deployed outputs:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;






&lt;h1&gt;
  
  
  🛡️ Security Considerations
&lt;/h1&gt;

&lt;p&gt;This project is designed as a practical cloud engineering and Infrastructure-as-Code portfolio project.&lt;/p&gt;

&lt;p&gt;Several production hardening improvements should be considered before using the configuration for a real production workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Current Security Design
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;RDS is deployed in private subnets.&lt;/li&gt;
&lt;li&gt;Database access is controlled through security groups.&lt;/li&gt;
&lt;li&gt;Production RDS is encrypted.&lt;/li&gt;
&lt;li&gt;Production RDS uses Multi-AZ.&lt;/li&gt;
&lt;li&gt;Production RDS has deletion protection enabled.&lt;/li&gt;
&lt;li&gt;Terraform state is stored remotely.&lt;/li&gt;
&lt;li&gt;Environment-specific configuration files are excluded from Git.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Recommended Improvements
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Replace direct SSH access with AWS Systems Manager Session Manager.&lt;/li&gt;
&lt;li&gt;Restrict SSH source IPs where SSH is required.&lt;/li&gt;
&lt;li&gt;Store database credentials in AWS Secrets Manager or Systems Manager Parameter Store.&lt;/li&gt;
&lt;li&gt;Place application instances in private subnets.&lt;/li&gt;
&lt;li&gt;Introduce an Application Load Balancer.&lt;/li&gt;
&lt;li&gt;Add CloudWatch monitoring and alarms.&lt;/li&gt;
&lt;li&gt;Implement least-privilege IAM policies.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  💰 Cost Considerations
&lt;/h1&gt;

&lt;p&gt;The infrastructure contains AWS resources that can generate ongoing costs, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NAT Gateways&lt;/li&gt;
&lt;li&gt;EC2 instances&lt;/li&gt;
&lt;li&gt;Auto Scaling Groups&lt;/li&gt;
&lt;li&gt;RDS&lt;/li&gt;
&lt;li&gt;Elastic IPs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For development and testing, unused environments should be destroyed when no longer required.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;terraform destroy &lt;span class="nt"&gt;-var-file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;terraform.tfvars
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Always verify the Terraform plan before destroying infrastructure.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h1&gt;
  
  
  🧠 Skills Demonstrated
&lt;/h1&gt;

&lt;h3&gt;
  
  
  Infrastructure as Code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Terraform&lt;/li&gt;
&lt;li&gt;Terraform Modules&lt;/li&gt;
&lt;li&gt;Remote State&lt;/li&gt;
&lt;li&gt;State Locking&lt;/li&gt;
&lt;li&gt;Environment Isolation&lt;/li&gt;
&lt;li&gt;Infrastructure Automation&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  AWS
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;VPC&lt;/li&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;Auto Scaling&lt;/li&gt;
&lt;li&gt;RDS&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;DynamoDB&lt;/li&gt;
&lt;li&gt;IAM&lt;/li&gt;
&lt;li&gt;Security Groups&lt;/li&gt;
&lt;li&gt;Network ACLs&lt;/li&gt;
&lt;li&gt;NAT Gateway&lt;/li&gt;
&lt;li&gt;Internet Gateway&lt;/li&gt;
&lt;li&gt;Route Tables&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DevOps
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;Infrastructure automation&lt;/li&gt;
&lt;li&gt;Environment management&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Modular infrastructure&lt;/li&gt;
&lt;li&gt;Public/private network segmentation&lt;/li&gt;
&lt;li&gt;High availability concepts&lt;/li&gt;
&lt;li&gt;Scalable compute&lt;/li&gt;
&lt;li&gt;Managed database architecture&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  📈 Future Improvements
&lt;/h1&gt;

&lt;p&gt;Planned improvements include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Application Load Balancer&lt;/li&gt;
&lt;li&gt;[ ] Private application subnets&lt;/li&gt;
&lt;li&gt;[ ] AWS Systems Manager integration&lt;/li&gt;
&lt;li&gt;[ ] CloudWatch dashboards and alarms&lt;/li&gt;
&lt;li&gt;[ ] GitHub Actions CI/CD&lt;/li&gt;
&lt;li&gt;[ ] Terraform plan checks on pull requests&lt;/li&gt;
&lt;li&gt;[ ] Infracost integration&lt;/li&gt;
&lt;li&gt;[ ] Secrets Manager integration&lt;/li&gt;
&lt;li&gt;[ ] Additional security hardening&lt;/li&gt;
&lt;li&gt;[ ] Cost optimization for NAT Gateways&lt;/li&gt;
&lt;li&gt;[ ] Automated infrastructure testing&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🎯 Project Purpose
&lt;/h1&gt;

&lt;p&gt;This project was built to demonstrate practical experience designing and deploying cloud infrastructure using &lt;strong&gt;Terraform and AWS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Rather than maintaining separate duplicated Terraform configurations, reusable modules are used across multiple environments.&lt;/p&gt;

&lt;p&gt;The project demonstrates how infrastructure can be:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reusable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scalable&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment-aware&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version controlled&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easier to maintain&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It forms part of my ongoing Cloud, DevOps, and Platform Engineering learning journey.&lt;/p&gt;




&lt;h1&gt;
  
  
  👨🏾‍💻 Author
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Jamiu Olatunji Bakre
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Cloud / DevOps Engineer | AWS | Terraform | Kubernetes | Platform Engineering&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I build and automate cloud infrastructure with a focus on AWS, Infrastructure as Code, DevOps practices, and scalable platform engineering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connect With Me
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/Jahmeeu-Cloud" rel="noopener noreferrer"&gt;Jahmeeu-Cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://linkedin.com/in/jamiu-bakre/" rel="noopener noreferrer"&gt;Jamiu Bakre&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Portfolio:&lt;/strong&gt; &lt;a href="https://jahmeeu-cloud.github.io/portfolio-project/" rel="noopener noreferrer"&gt;CloudWithHorla Portfolio&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ⭐ If you find this project useful
&lt;/h2&gt;

&lt;p&gt;Feel free to explore the repository, review the Terraform modules, and follow the project as it evolves.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Built with Terraform + AWS ☁️&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>terraform</category>
      <category>platformengineer</category>
      <category>github</category>
    </item>
    <item>
      <title>I’m Learning Kubernetes—Here Are the Mistakes I Made So You Don’t Have To</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Sun, 30 Aug 2026 01:26:51 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/im-learning-kubernetes-here-are-the-mistakes-i-made-so-you-dont-have-to-e8i</link>
      <guid>https://dev.to/jamiu_cloud/im-learning-kubernetes-here-are-the-mistakes-i-made-so-you-dont-have-to-e8i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;If you have ever stared at a Pod stuck in ContainerCreating and wondered what you did wrong, this one is for you. These are the mistakes I made while learning Kubernetes — and the lessons that finally clicked.&lt;/p&gt;
&lt;/blockquote&gt;

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

&lt;p&gt;There is a version of Kubernetes that exists in beginner tutorials, and there is the version you meet when you actually start using it. In the tutorial version, everything looks clean: create a Pod, expose a Service, scale replicas, move on. In real life, there are drivers that are missing, Docker permissions that block you, images that take time to pull, Services that do not exist yet, namespaces that quietly change what you are looking at, and Pods that disappear only to come back because Kubernetes is doing exactly what it was designed to do.&lt;/p&gt;

&lt;p&gt;This is not a story about becoming a Kubernetes expert overnight. It is a story about getting my hands dirty, making honest mistakes, troubleshooting them one by one, and gradually understanding why organizations trust Kubernetes to run production workloads.&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%2Faw4b22i400sq9p419cbz.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%2Faw4b22i400sq9p419cbz.png" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  1. My starting point
&lt;/h2&gt;

&lt;p&gt;I started by learning the foundations: what Kubernetes is, why companies use it, what a cluster is, the difference between the control plane and worker nodes, what nodes and Pods are, how containers differ from Pods, and how Docker and Kubernetes relate to each other. I also worked with &lt;code&gt;kubectl&lt;/code&gt;, Minikube, namespaces, Deployments, ReplicaSets, Services, scaling, Helm, and later Kubeshark.&lt;/p&gt;

&lt;p&gt;On paper, that list looked manageable. In practice, every concept became clearer only when it broke in front of me.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. What I thought Kubernetes was (and what I misunderstood)
&lt;/h2&gt;

&lt;p&gt;At the beginning, I thought Kubernetes was basically Docker at a bigger scale. That assumption did not survive long. The more I used it, the more I saw that Kubernetes is not mostly about starting containers. It is about describing a &lt;strong&gt;desired state and letting the cluster&lt;/strong&gt; keep moving toward that state.&lt;/p&gt;

&lt;p&gt;A few of my early wrong assumptions:&lt;/p&gt;

&lt;p&gt;Minikube would just start, without needing a supported driver or runtime such as Docker.&lt;br&gt;
A Pod in &lt;code&gt;ContainerCreating&lt;/code&gt; meant something was definitely broken.&lt;br&gt;
Deleting a Pod meant I had removed the application.&lt;br&gt;
I could use &lt;code&gt;minikube service&lt;/code&gt; before creating any Service.&lt;br&gt;
Every failure during Helm or Kubeshark setup had to be a Kubernetes problem.&lt;/p&gt;


&lt;h2&gt;
  
  
  3. Setup: Minikube, Docker, and permissions
&lt;/h2&gt;

&lt;p&gt;One of my first real lessons came from setup. I tried &lt;code&gt;minikube start&lt;/code&gt; before having a supported driver installed. That reminded me: Kubernetes on a laptop still depends on what is underneath it. Minikube is not magic. It needs a runtime/driver to create and run the local cluster.&lt;/p&gt;

&lt;p&gt;Then came Docker permission issues, which led me to learn about the Docker group and why a user gets blocked from Docker commands until the right permissions are in place.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Early Cloud/DevOps lesson: when something “Kubernetes-related” fails, it is sometimes the environment around Kubernetes that needs attention first.&lt;/p&gt;
&lt;/blockquote&gt;


&lt;h2&gt;
  
  
  4. My first Pod and the patience it taught me
&lt;/h2&gt;

&lt;p&gt;I created an &lt;strong&gt;NGINX Pod&lt;/strong&gt; and saw it sit in &lt;code&gt;ContainerCreating&lt;/code&gt;. My first instinct was that something was wrong. Eventually it became &lt;code&gt;Running&lt;/code&gt;. That moment sounded small, but it mattered. Container images may need to be downloaded, Internet access matters, and a Pod &lt;strong&gt;not&lt;/strong&gt; being ready immediately does not automatically mean failure.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The lesson: instead of treating every delay as an error, I started asking, “What is Kubernetes doing right now?”&lt;/em&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  5. Deployments, ReplicaSets, and reconciliation
&lt;/h2&gt;

&lt;p&gt;The biggest “click” moment came when I deleted a Pod and watched Kubernetes recreate it. I had deleted a Pod that was managed by a Deployment. The Deployment still wanted the desired number of replicas, so the cluster created another Pod to bring the state back in line.&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%2F784kfjau939pg7v7m094.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%2F784kfjau939pg7v7m094.png" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That single experience taught me more than several pages of explanation could. It made Deployments, ReplicaSets, and reconciliation feel real.&lt;/p&gt;
&lt;h2&gt;
  
  
  6. Services — and how I learned what &lt;code&gt;SVC_NOT_FOUND&lt;/code&gt; really means
&lt;/h2&gt;

&lt;p&gt;Services answered a question I kept running into: how do I reliably reach an application when Pods are temporary? A Pod can be recreated and get a different IP. A Service gives that changing set of Pods a stable endpoint.&lt;/p&gt;

&lt;p&gt;I learned this when I ran:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;before I had created any Service. The response was SVC_NOT_FOUND. My assumption was the bug, not Minikube. The fix was to create the Service explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl expose pod nginx &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NodePort &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  7. Scaling
&lt;/h2&gt;

&lt;p&gt;Scaling replicas sounds simple when you read it, but it becomes more meaningful when you watch it happen. Increasing replica count means the Deployment updates the ReplicaSet, and the ReplicaSet creates more Pods until the cluster reaches the new desired count. That is when Kubernetes starts feeling less like manual container management and more like orchestration.&lt;/p&gt;




&lt;h2&gt;
  
  
  8. Troubleshooting  🛠️
&lt;/h2&gt;

&lt;p&gt;The most valuable part of this journey has been troubleshooting. Good troubleshooting is not random trial and error. It is a sequence:&lt;/p&gt;

&lt;p&gt;Read the error.&lt;br&gt;
Question the assumption behind the step.&lt;br&gt;
Inspect the cluster state.&lt;br&gt;
Compare expected behavior with actual behavior.&lt;br&gt;
Isolate the real cause.&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%2Fid2yoe26j3apaxksto7d.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%2Fid2yoe26j3apaxksto7d.png" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  9. Helm &amp;amp; Kubeshark — and the layer-aware lesson
&lt;/h2&gt;

&lt;p&gt;Helm clicked for me as a package manager for Kubernetes. Then came Kubeshark. I ran into repository connectivity problems and almost blamed Kubernetes immediately. The more useful question was: is this a cluster problem, or is the repository itself unreachable from my environment?&lt;/p&gt;

&lt;p&gt;curl -I &lt;a href="https://helm.kubeshark.com/index.yaml" rel="noopener noreferrer"&gt;https://helm.kubeshark.com/index.yaml&lt;/a&gt;&lt;br&gt;
curl -I &lt;a href="https://google.com" rel="noopener noreferrer"&gt;https://google.com&lt;/a&gt;&lt;br&gt;
curl -I &lt;a href="https://github.com" rel="noopener noreferrer"&gt;https://github.com&lt;/a&gt;&lt;br&gt;
curl -I &lt;a href="https://raw.githubusercontent.com" rel="noopener noreferrer"&gt;https://raw.githubusercontent.com&lt;/a&gt;&lt;br&gt;
That comparison mattered. It helped me distinguish between “my Kubernetes or Helm setup is broken” and “this particular external repository cannot be reached.” Systems fail in layers. Good troubleshooting means identifying which layer is actually failing.&lt;/p&gt;




&lt;h2&gt;
  
  
  10. Mistakes I made — and what each one taught me
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;MISTAKE&lt;/th&gt;
&lt;th&gt;LESSON&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Starting Minikube without a supported driver&lt;/td&gt;
&lt;td&gt;Kubernetes tools still depend on the host environment. Drivers, runtimes, and permissions matter.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Treating &lt;code&gt;ContainerCreating&lt;/code&gt; as immediate failure&lt;/td&gt;
&lt;td&gt;Sometimes the image is still downloading. Read the state in context and inspect events before panicking.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trying &lt;code&gt;minikube service&lt;/code&gt; before creating a Service&lt;/td&gt;
&lt;td&gt;A Pod is not automatically a Service. Reachability needs to be defined explicitly.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deleting a Pod and expecting the app to be gone&lt;/td&gt;
&lt;td&gt;Deployments preserve the desired replica count. Delete the Deployment if you want the managed Pods to stop returning.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Working in the wrong namespace&lt;/td&gt;
&lt;td&gt;Always confirm the namespace context. Resources can be healthy while you are looking in the wrong place.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assuming every Helm/Kubeshark issue was a Kubernetes issue&lt;/td&gt;
&lt;td&gt;Sometimes the real problem is repository/network access outside the cluster. Verify the failing layer.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  11. Commands that actually helped me
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;minikube start
kubectl get pods &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl get deployments &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl get services &lt;span class="nt"&gt;-A&lt;/span&gt;
kubectl describe pod &amp;lt;pod-name&amp;gt;
kubectl logs &amp;lt;pod-name&amp;gt;
kubectl get deployment nginx-deployment &lt;span class="nt"&gt;-o&lt;/span&gt; yaml
kubectl expose pod nginx &lt;span class="nt"&gt;--type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;NodePort &lt;span class="nt"&gt;--port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;80
minikube service &amp;lt;service-name&amp;gt; &lt;span class="nt"&gt;--url&lt;/span&gt;
curl &lt;span class="nt"&gt;-I&lt;/span&gt; https://helm.kubeshark.com/index.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  12. Lessons for other beginners
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do not rush past the basic architecture. Knowing the relationship between Deployments, ReplicaSets, Pods, and Services saves time later.&lt;/li&gt;
&lt;li&gt;Read the exact error message before trying random commands.&lt;/li&gt;
&lt;li&gt;Check namespaces early. It is one of the easiest ways to eliminate confusion.&lt;/li&gt;
&lt;li&gt;If a Pod is managed by a Deployment, deleting the Pod is not the same as deleting the application.&lt;/li&gt;
&lt;li&gt;When troubleshooting, identify the failing layer: local machine, Docker runtime, Minikube, Kubernetes resource, network access, or external repository.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I now understand
&lt;/h2&gt;

&lt;p&gt;Kubernetes is not about micromanaging containers. It is about declaring state, letting the cluster maintain it, and using higher-level abstractions to make applications resilient and reachable.&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%2F6juf8hiv5c8an50zmdum.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%2F6juf8hiv5c8an50zmdum.png" alt=" " width="800" height="514"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I still need to learn
&lt;/h2&gt;

&lt;p&gt;There is still a lot ahead: Ingress, ConfigMaps, Secrets, persistent storage, observability, production-grade networking, security, and more advanced debugging patterns. The basics are no longer abstract terms — they now connect to things I have actually seen fail and recover.&lt;/p&gt;

&lt;h2&gt;
  
  
  Next stop: Kubernetes Ingress 🚦
&lt;/h2&gt;

&lt;p&gt;Ingress is the natural next topic because it ties everything together. Pods run the application, Services give a stable way to reach those Pods inside the cluster, and Ingress sits above that to route external HTTP/HTTPS traffic to the right Service. If Services help internal reachability, Ingress helps organize external access more cleanly.&lt;/p&gt;

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

&lt;p&gt;Every time Minikube refused to start, every time a Pod sat in &lt;code&gt;ContainerCreating&lt;/code&gt;, every time a Service did not exist yet, every time a namespace caused confusion, every time a Pod came back after I deleted it — Kubernetes was teaching me something useful.&lt;/p&gt;

&lt;p&gt;I thought learning Kubernetes would mostly be about commands. I was wrong. It has been more about systems thinking, careful troubleshooting, and learning how to ask better questions.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Next stop:&lt;/strong&gt; Kubernetes Ingress — understanding how external traffic reaches Services, and through them, the Pods behind the application.&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>kubernetes</category>
      <category>docker</category>
      <category>kubeshark</category>
      <category>containers</category>
    </item>
    <item>
      <title>📘 Ansible Learning Journey - From Manual SSH to Automation Thinking</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Wed, 14 Jan 2026 15:07:46 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/ansible-learning-journey-from-manual-ssh-to-automation-thinking-2a2o</link>
      <guid>https://dev.to/jamiu_cloud/ansible-learning-journey-from-manual-ssh-to-automation-thinking-2a2o</guid>
      <description>&lt;p&gt;&lt;strong&gt;CloudWithHorla | Cloud &amp;amp; DevOps Engineer (Learning in Progress 🚀)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Automation looks simple from the outside — until you try to build it yourself.&lt;/p&gt;

&lt;p&gt;Recently, I worked on an &lt;strong&gt;Ansible hands-on project&lt;/strong&gt; as part of my continuous learning in Cloud and DevOps engineering. The goal was simple on paper:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;set up a controller node and manage multiple target nodes using Ansible&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu3bqh7aqj4sj2fruuivf.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%2Fu3bqh7aqj4sj2fruuivf.png" alt=" " width="799" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In reality, the journey taught me far more than just commands.&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ Project Overview
&lt;/h2&gt;

&lt;p&gt;I built a small but realistic infrastructure on AWS consisting of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;* &lt;strong&gt;1 EC2 Controller (Ansible Master)&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;* &lt;strong&gt;3 EC2 Target Nodes&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;* Ubuntu-based servers&lt;/li&gt;
&lt;li&gt;* SSH key-based authentication&lt;/li&gt;
&lt;li&gt;* Ansible ad-hoc commands and playbooks&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%2Fi4oftonmq3pvn8e12k6s.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%2Fi4oftonmq3pvn8e12k6s.png" alt=" " width="800" height="169"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This setup mirrors what actually happens in real-world environments — not just labs.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔧 What I Implemented (Step-by-Step)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Provisioned EC2 instances&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;1 controller&lt;/li&gt;
&lt;li&gt;3 managed nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Installed core dependencies&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Python3&lt;/li&gt;
&lt;li&gt;Pip&lt;/li&gt;
&lt;li&gt;Ansible on the controller&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Created Ansible configuration&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Custom &lt;code&gt;ansible.cfg&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Inventory file defining all target nodes&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SSH Key-Based Authentication&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Generated SSH key pair on the controller&lt;/li&gt;
&lt;li&gt;Injected the controller’s &lt;strong&gt;public key&lt;/strong&gt; into each node’s &lt;code&gt;authorized_keys&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Learned the difference between:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt;  &lt;span class="c"&gt;# append (safe)&lt;/span&gt;
 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   &lt;span class="c"&gt;# overwrite (dangerous)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

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

&lt;p&gt;You are adding a new line of Public Key which is safe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCppACFB47TRDuT8YXSSxL+T9jxirz0yC04avxRB8nAOnVcz5xXpyT0mhYbhcLCbI2D4pcWkSb1NqGyUwF3OeHGtur+0MkDq7/9jhPG0amrFbk+RBQM+WZ7UaEzopiK7ZB8MUKwtMGVhR9wgtIiSK6+fgmzNaCpfj13B9aH7iiMRn4sHCwr81zCAskb79DnhCmB8Y7NU2VAqlMnW5NIFEujv3aixmPSZbIKomNotGT2ljtOV5EoMuFFqq3DqA/kZVM1WJjBMEAlWtdQTn2UM5dTAi5BNvPMYJcdzEfc4fXvkp11rFYLiEc9ZUFkYi6APg1Ju5bPgh0Yp+9YOhlCMEuEPlAdxUIHE4Ih3HoNhLOB622c+a6lNK5SF7Y76gB5cdK8Rar/tRQqF6kxqy852SLfZbLnXkfIOuktF4XGemMMt13F7yw1k980vwcwCgfAK4zOZ2hmaDypmG0Rd/lhsIpC+4i6s4T8a+AB3BSKt9KH/Dn9cxUM0BVgxgM1hWyk7Hs= ubuntu@ip-10-0-8-231" &amp;gt;&amp;gt; .ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caveat: Best practise&lt;/p&gt;

&lt;p&gt;You are removing an existing "Public Key" which is not safe.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;echo "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCppACFB47TRDuT8YXSSxL+T9jxirz0yC04avxRB8nAOnVcz5xXpyT0mhYbhcLCbI2D4pcWkSb1NqGyUwF3OeHGtur+0MkDq7/9jhPG0amrFbk+RBQM+WZ7UaEzopiK7ZB8MUKwtMGVhR9wgtIiSK6+fgmzNaCpfj13B9aH7iiMRn4sHCwr81zCAskb79DnhCmB8Y7NU2VAqlMnW5NIFEujv3aixmPSZbIKomNotGT2ljtOV5EoMuFFqq3DqA/kZVM1WJjBMEAlWtdQTn2UM5dTAi5BNvPMYJcdzEfc4fXvkp11rFYLiEc9ZUFkYi6APg1Ju5bPgh0Yp+9YOhlCMEuEPlAdxUIHE4Ih3HoNhLOB622c+a6lNK5SF7Y76gB5cdK8Rar/tRQqF6kxqy852SLfZbLnXkfIOuktF4XGemMMt13F7yw1k980vwcwCgfAK4zOZ2hmaDypmG0Rd/lhsIpC+4i6s4T8a+AB3BSKt9KH/Dn9cxUM0BVgxgM1hWyk7Hs= ubuntu@ip-10-0-8-231" &amp;gt; .ssh/authorized_keys
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Caveat: Not a best practise&lt;/p&gt;

&lt;p&gt;Sample &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Privilege Escalation&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Used &lt;code&gt;become: true&lt;/code&gt; for system-level tasks&lt;/li&gt;
&lt;li&gt;Ensured sudo access without breaking security&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ansible Ad-Hoc Commands&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ansible &lt;span class="nt"&gt;-m&lt;/span&gt; apt &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="s2"&gt;"name=git state=present"&lt;/span&gt; all &lt;span class="nt"&gt;-b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Ansible Playbook Execution&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;Installed and removed packages&lt;/li&gt;
&lt;li&gt;Copied files to remote nodes&lt;/li&gt;
&lt;li&gt;Managed services across multiple machines in parallel&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📄 Sample Playbook used in my project:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&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;Installing nginx playbook&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;all&lt;/span&gt;
  &lt;span class="na"&gt;become&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;tasks&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;First Task&lt;/span&gt;
      &lt;span class="na"&gt;apt&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;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;absent&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;Second Task&lt;/span&gt;
      &lt;span class="na"&gt;apt&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;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&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;Copy file with owner and permissions&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./index.html&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/index.html&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;root&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0777'&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;Third Task&lt;/span&gt;
      &lt;span class="na"&gt;apt&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;apache2&lt;/span&gt;
        &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;present&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  ⚠️ Challenges I Faced (and What I Learned)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1️⃣ “Permission denied (publickey)”
&lt;/h3&gt;

&lt;p&gt;I initially thought Ansible could manage nodes &lt;strong&gt;without SSH access&lt;/strong&gt;.&lt;br&gt;
Through research and troubleshooting, I learned a critical truth:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Ansible always uses SSH. There is no automation without an initial trust path.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Key-based authentication must be established &lt;strong&gt;at least once&lt;/strong&gt; — either at instance creation or manually.&lt;/p&gt;




&lt;h3&gt;
  
  
  2️⃣ PasswordAuthentication Confusion
&lt;/h3&gt;

&lt;p&gt;I tried disabling password authentication &lt;em&gt;before&lt;/em&gt; confirming key-based access.&lt;/p&gt;

&lt;p&gt;That was a big lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;❌ Disabling &lt;code&gt;PasswordAuthentication&lt;/code&gt; does NOT give access&lt;br&gt;
✅ It only removes password login &lt;strong&gt;after SSH keys already work&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Correct order matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Establish SSH key access&lt;/li&gt;
&lt;li&gt;Test connectivity&lt;/li&gt;
&lt;li&gt;Then disable password authentication&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  3️⃣ APT Lock Errors (&lt;code&gt;unattended-upgrades&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;One node failed while others succeeded.&lt;/p&gt;

&lt;p&gt;Cause:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ubuntu auto-updates were running in the background&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Infrastructure is &lt;strong&gt;not deterministic&lt;/strong&gt; — nodes behave independently.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Solution:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Wait and retry&lt;/li&gt;
&lt;li&gt;Or design playbooks with retries and checks&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Screenshot Output&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%2Fa21okez4i0b57ntn8u1p.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%2Fa21okez4i0b57ntn8u1p.png" alt=" " width="800" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is when I ping of the node&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%2Fxy3l3mke9kao6b9nbwr1.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%2Fxy3l3mke9kao6b9nbwr1.png" alt=" " width="800" height="295"&gt;&lt;/a&gt;&lt;br&gt;
This is when I ping all the node&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%2Fj3fa4t23n1o2u75s1nc7.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%2Fj3fa4t23n1o2u75s1nc7.png" alt=" " width="800" height="322"&gt;&lt;/a&gt;&lt;br&gt;
This is the first page output of my Playbook&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%2F7qua607uilk36clhlpzg.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%2F7qua607uilk36clhlpzg.png" alt=" " width="799" height="201"&gt;&lt;/a&gt;&lt;br&gt;
This is the second page output of my Playbook&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%2Foncy46lc4mzzf72n3wog.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%2Foncy46lc4mzzf72n3wog.png" alt=" " width="800" height="458"&gt;&lt;/a&gt;&lt;br&gt;
Node 01&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%2Faf5vhb0d3z0fmj90kuub.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%2Faf5vhb0d3z0fmj90kuub.png" alt=" " width="757" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Node 02&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%2Fm9toag8k4la1ar2yo4ol.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%2Fm9toag8k4la1ar2yo4ol.png" alt=" " width="588" height="409"&gt;&lt;/a&gt;&lt;br&gt;
Node 03&lt;/p&gt;




&lt;h3&gt;
  
  
  4️⃣ Wrong Package Names
&lt;/h3&gt;

&lt;p&gt;I attempted to install &lt;code&gt;apache&lt;/code&gt; instead of &lt;code&gt;apache2&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Lesson:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Automation does exactly what you tell it — &lt;strong&gt;not what you mean&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Small naming errors can break entire workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 Key Takeaways I Want Others to Learn
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;🔑 &lt;strong&gt;SSH is foundational&lt;/strong&gt; — Ansible cannot bypass it&lt;/li&gt;
&lt;li&gt;🔁 Automation is about &lt;strong&gt;idempotency and retries&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;🧪 Always test with &lt;code&gt;--syntax-check&lt;/code&gt; and &lt;code&gt;ping&lt;/code&gt; yourself (localhost) for test.&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%2Fx8ebyk4ih18zogvemebi.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%2Fx8ebyk4ih18zogvemebi.png" alt=" " width="800" height="286"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔐 Never overwrite &lt;code&gt;authorized_keys&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;🛠️ Real learning happens when things break&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🌱 Growth Mindset
&lt;/h2&gt;

&lt;p&gt;This project reinforced my core belief:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never stop learning.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Every error I faced forced me to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Research deeper&lt;/li&gt;
&lt;li&gt;Read documentation&lt;/li&gt;
&lt;li&gt;Understand &lt;em&gt;why&lt;/em&gt; things work — not just &lt;em&gt;how&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’m still learning.&lt;br&gt;
I’m still breaking things.&lt;br&gt;
And I’m still getting better — one tool at a time.&lt;/p&gt;




&lt;h3&gt;
  
  
  📌 Final Thought
&lt;/h3&gt;

&lt;p&gt;Automation isn’t about avoiding work —&lt;br&gt;
it’s about &lt;strong&gt;doing the work once, correctly and at scale&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;CloudWithHorla ☁️&lt;/strong&gt;&lt;br&gt;
Cloud &amp;amp; DevOps Engineer | Always Learning&lt;/p&gt;

</description>
      <category>aws</category>
      <category>ansible</category>
      <category>automation</category>
      <category>devops</category>
    </item>
    <item>
      <title>My Ongoing Cloud Migration Journey 🚀</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Mon, 10 Nov 2025 16:22:16 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/my-ongoing-cloud-migration-journey-3i1a</link>
      <guid>https://dev.to/jamiu_cloud/my-ongoing-cloud-migration-journey-3i1a</guid>
      <description>&lt;p&gt;Over the past few months, I’ve been working on one of the most exciting and challenging cloud migration projects of my career, a large-scale transformation initiative between &lt;strong&gt;xyz&lt;/strong&gt; (a global technology consulting firm) and &lt;strong&gt;abc&lt;/strong&gt; (a major telecommunications company).&lt;/p&gt;

&lt;p&gt;The goal of this project is simple but powerful: &lt;strong&gt;to move dozens of critical enterprise applications from on-premises data centers into a secure, scalable, and automated AWS environment&lt;/strong&gt; without disrupting existing business operations. This is part of a broader modernization program that will allow abc to run its workloads faster, more securely, and more efficiently in the cloud.&lt;/p&gt;




&lt;h3&gt;
  
  
  What We’re Doing
&lt;/h3&gt;

&lt;p&gt;The migration involves a mix of rehosting, redeploying, and refactoring applications using &lt;strong&gt;AWS native tools, Terraform for Infrastructure as Code (IaC), and automated CI/CD pipelines&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Our scope includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Designing and deploying AWS environments for multiple business units.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Setting up &lt;strong&gt;VPCs, subnets, IAM roles, security groups, load balancers, and monitoring tools&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implementing &lt;strong&gt;cross-account role assumption&lt;/strong&gt; for secure access and governance.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Coordinating closely with both the cloud engineering and application teams to ensure smooth transitions.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As of now, we’re working through a pipeline of &lt;strong&gt;over 30 applications&lt;/strong&gt; scheduled for migration to AWS. Several have already been successfully redeployed and tested in non-production and production environments.&lt;/p&gt;




&lt;h3&gt;
  
  
  My Role
&lt;/h3&gt;

&lt;p&gt;As a Cloud Engineer on this project, my role cuts across technical execution, documentation, and coordination. I’ve been directly involved in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Designing AWS architectures&lt;/strong&gt; for multiple applications; defining network layout, IAM roles, and access controls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Collaborating on Terraform automation&lt;/strong&gt;, including maintaining GitHub repositories and building modular scripts for consistent infrastructure deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Managing environment readiness&lt;/strong&gt;, which includes server provisioning, IAM access configuration, and validating connectivity between on-prem databases and cloud servers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supporting MGN setup&lt;/strong&gt;, ensuring source servers are correctly configured for migration replication.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Contributing to migration design documents&lt;/strong&gt;, including the logical architecture, deployment models, dependencies, and database connections.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Coordinating with the application and security teams&lt;/strong&gt; to manage firewall requests, DNS cutovers, and access provisioning.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  What I’ve Achieved So Far
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Successfully supported the &lt;strong&gt;AWS environment setup&lt;/strong&gt; for multiple application migrations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Created and refined design documentation&lt;/strong&gt; that clearly outlines migration strategy, deployment processes, and technical architecture.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Validated IAM roles and cross-account access&lt;/strong&gt;, ensuring secure provisioning in multi-account AWS setups.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Helped establish a &lt;strong&gt;Terraform-based deployment workflow&lt;/strong&gt;, integrating version control and infrastructure automation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Worked closely with the project leads to &lt;strong&gt;align AWS provisioning with organizational compliance standards&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Gained practical experience in &lt;strong&gt;AWS MGN (Migration Hub)&lt;/strong&gt;, &lt;strong&gt;Terraform automation&lt;/strong&gt;, and &lt;strong&gt;enterprise migration governance&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Where We Are Now
&lt;/h3&gt;

&lt;p&gt;Our team has completed the foundation setup AWS accounts, networking, IAM, and automation frameworks are now in place. We’re currently focused on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accelerating AWS application deployments through Terraform automation.&lt;/li&gt;
&lt;li&gt;Coordinating with database and app owners for production cutovers.&lt;/li&gt;
&lt;li&gt;Streamlining server provisioning and post-deployment checks.&lt;/li&gt;
&lt;li&gt;Ensuring security compliance and monitoring with AWS CloudWatch and Config.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  What Makes This Experience Stand Out
&lt;/h3&gt;

&lt;p&gt;This project is more than just moving workloads to AWS; it’s a &lt;strong&gt;complete cloud enablement initiative&lt;/strong&gt;. I’ve learned the value of collaboration between cloud engineers, app owners, database administrators, and project managers, all working toward a shared goal of modernization.&lt;/p&gt;

&lt;p&gt;Every day comes with new learning: understanding enterprise migration frameworks, managing IAM at scale, troubleshooting deployment issues, and optimizing Terraform code for real-world production environments.&lt;/p&gt;




&lt;h3&gt;
  
  
  My Reflection
&lt;/h3&gt;

&lt;p&gt;This experience has strengthened my skills not only as a Cloud Engineer but as a problem solver and collaborator. From designing secure AWS architectures to supporting production rollouts, I’ve seen firsthand how cloud migration works at scale the challenges, the coordination, and the satisfaction that comes when an application successfully goes live in the cloud.&lt;/p&gt;

&lt;p&gt;We’re still moving forward, with many more applications to migrate in the coming weeks. And as we continue to build, automate, and refine, I’m proud to be part of a team that’s shaping the future of cloud transformation for one of the biggest enterprises in the industry.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>mgn</category>
      <category>terraform</category>
      <category>git</category>
    </item>
    <item>
      <title>Building a Scalable AWS E-Learning Platform for Government Use</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Sat, 18 Oct 2025 13:37:45 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/building-a-scalable-aws-e-learning-platform-for-government-use-hb5</link>
      <guid>https://dev.to/jamiu_cloud/building-a-scalable-aws-e-learning-platform-for-government-use-hb5</guid>
      <description>&lt;h3&gt;
  
  
  🌍 Project Overview
&lt;/h3&gt;

&lt;p&gt;As part of a Architect Engineer assessment for &lt;strong&gt;XYZ Company&lt;/strong&gt;, I was tasked with designing a &lt;strong&gt;secure, scalable, and reliable AWS-based architecture&lt;/strong&gt; for a &lt;strong&gt;government-backed e-learning platform&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal was to create a system capable of serving &lt;strong&gt;up to 200 million users&lt;/strong&gt; across multiple regions — supporting video streaming, quizzes, progress tracking, and a multilingual AI chatbot.&lt;/p&gt;




&lt;h3&gt;
  
  
  🧱 Architecture Goal
&lt;/h3&gt;

&lt;p&gt;Design a &lt;strong&gt;3-tier AWS architecture&lt;/strong&gt; that ensures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scalability&lt;/strong&gt; for millions of users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security and compliance&lt;/strong&gt; with government data standards&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;High availability&lt;/strong&gt; across multiple Availability Zones&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost efficiency&lt;/strong&gt; using serverless and auto-scaling resources&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  ⚙️ Core AWS Services Used
&lt;/h3&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;1️⃣ User Authentication &amp;amp; Access Management&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Cognito&lt;/strong&gt; for secure user authentication&lt;/li&gt;
&lt;li&gt;Integrated with &lt;strong&gt;government identity providers&lt;/strong&gt; via &lt;strong&gt;SAML/OAuth&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-Factor Authentication (MFA)&lt;/strong&gt; enabled for all users&lt;/li&gt;
&lt;li&gt;Role-based access for students, instructors, and administrators&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;2️⃣ Video Delivery and Streaming&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon S3&lt;/strong&gt; stores all course materials and videos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Elemental MediaConvert&lt;/strong&gt; handles video transcoding&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon CloudFront (CDN)&lt;/strong&gt; delivers content with adaptive bitrate streaming for smooth playback across all regions&lt;/li&gt;
&lt;li&gt;Content is secured using &lt;strong&gt;signed URLs&lt;/strong&gt; and &lt;strong&gt;S3 encryption&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;3️⃣ Multilingual AI Chatbot&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Lex&lt;/strong&gt; powers the conversational interface&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon Translate&lt;/strong&gt; automatically detects and translates languages&lt;/li&gt;
&lt;li&gt;Chatbot supports &lt;strong&gt;English, Yoruba, Hausa, Igbo, and Pidgin&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Personalized responses based on user progress data stored in Aurora&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;4️⃣ Monitoring &amp;amp; Compliance&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AWS CloudTrail&lt;/strong&gt; logs all API activities for audit purposes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Amazon CloudWatch&lt;/strong&gt; monitors performance metrics and triggers alarms&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Config&lt;/strong&gt; ensures resource compliance with security policies&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;5️⃣ Quizzes &amp;amp; Progress Tracking&lt;/strong&gt;
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Real-time quiz scoring and feedback&lt;/strong&gt; using &lt;strong&gt;AWS Lambda + API Gateway&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Data stored in &lt;strong&gt;Amazon Aurora (PostgreSQL)&lt;/strong&gt; for structured consistency&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DynamoDB&lt;/strong&gt; used for low-latency user progress tracking&lt;/li&gt;
&lt;/ul&gt;




&lt;h4&gt;
  
  
  &lt;strong&gt;6️⃣ Data Storage &amp;amp; Analytics&lt;/strong&gt;
&lt;/h4&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Data Type&lt;/th&gt;
&lt;th&gt;AWS Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Videos &amp;amp; Materials&lt;/td&gt;
&lt;td&gt;Amazon S3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User &amp;amp; Quiz Data&lt;/td&gt;
&lt;td&gt;Amazon Aurora&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Progress Tracking&lt;/td&gt;
&lt;td&gt;Amazon DynamoDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics &amp;amp; Reporting&lt;/td&gt;
&lt;td&gt;AWS Glue + Redshift (future phase)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;Here is the Native Design below:&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%2Frocm1jj64ug3qfib1gbm.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%2Frocm1jj64ug3qfib1gbm.png" alt=" " width="800" height="504"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  🏗️ 3-Tier Architecture Design
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Tier 1: Web Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deployed on &lt;strong&gt;Auto-Scaled EC2 instances&lt;/strong&gt; behind an &lt;strong&gt;Application Load Balancer (ALB)&lt;/strong&gt; in &lt;strong&gt;public subnets&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tier 2: Application Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stateless compute services (EC2 or AWS Lambda) in &lt;strong&gt;private subnets&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Tier 3: Database Layer&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aurora Cluster&lt;/strong&gt; in &lt;strong&gt;private subnets&lt;/strong&gt;, Multi-AZ enabled with read replicas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Additional Infrastructure:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;VPC&lt;/strong&gt; with 6 subnets (3 public, 3 private across 3 AZs)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateway&lt;/strong&gt; for private subnet outbound access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security Groups&lt;/strong&gt; + &lt;strong&gt;Network ACLs&lt;/strong&gt; for layered protection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront CDN&lt;/strong&gt; for global performance acceleration&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  💰 Cost Optimization Highlights
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aurora Serverless v2&lt;/strong&gt; for automatic scaling of database capacity&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;S3 Intelligent-Tiering&lt;/strong&gt; for optimizing storage costs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spot Instances&lt;/strong&gt; for EC2 auto-scaling groups&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Budgets + Cost Explorer&lt;/strong&gt; for continuous cost monitoring&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Highly available&lt;/strong&gt;, &lt;strong&gt;fault-tolerant&lt;/strong&gt;, and &lt;strong&gt;secure&lt;/strong&gt; architecture&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimized for millions of concurrent users&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Ready for &lt;strong&gt;NDPR/ISO compliance&lt;/strong&gt; via CloudTrail and IAM governance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Inclusive learning&lt;/strong&gt; through multilingual AI support&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  🧾 Deliverables
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;AWS Architecture Diagram&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Statement of Work (SOW)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Presentation Slide Deck&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  🚀 Conclusion
&lt;/h3&gt;

&lt;p&gt;This project demonstrates the power of &lt;strong&gt;cloud-native architecture on AWS&lt;/strong&gt; in enabling &lt;strong&gt;education at scale&lt;/strong&gt; — bringing inclusive, accessible, and secure learning to millions of users across regions.&lt;/p&gt;

&lt;p&gt;It also highlights my practical experience as a &lt;strong&gt;Cloud Engineer&lt;/strong&gt; in building enterprise-grade solutions that combine &lt;strong&gt;scalability, security, and innovation&lt;/strong&gt;.&lt;/p&gt;




&lt;h3&gt;
  
  
  📬 Connect With Me
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;👤 Bakre Jamiu (CloudWithHorla)&lt;/strong&gt;&lt;br&gt;
AWS Cloud &amp;amp; DevOps Engineer&lt;br&gt;
✉️ &lt;a href="mailto:bakrejamiu@gmail.com"&gt;bakrejamiu@gmail.com&lt;/a&gt;&lt;/p&gt;

</description>
      <category>aws</category>
      <category>architectengineer</category>
      <category>cloudnative</category>
      <category>nlp</category>
    </item>
    <item>
      <title>🚀 Optimizing Costs Without Downtime: My Hands-On Experience Downgrading AWS Lightsail; A Step-by-Step Journey</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Wed, 06 Aug 2025 15:46:27 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/optimizing-costs-without-downtime-my-hands-on-experience-downgrading-aws-lightsail-a-jaf</link>
      <guid>https://dev.to/jamiu_cloud/optimizing-costs-without-downtime-my-hands-on-experience-downgrading-aws-lightsail-a-jaf</guid>
      <description>&lt;p&gt;Over 3 days, I planned, executed, and documented the entire process. Here’s how I successfully completed the downgrade while maintaining service availability.&lt;/p&gt;

&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In cloud engineering, cost optimization without compromising performance is a skill every professional must master. Recently, I faced a real-world challenge: one of my projects running on AWS Lightsail required a downgrade to a smaller instance to reduce monthly costs. The catch? I had to ensure zero downtime and data integrity.&lt;/p&gt;

&lt;h1&gt;
  
  
  🛠 Project Objective
&lt;/h1&gt;

&lt;p&gt;Task: Downgrade AWS Lightsail instance to a smaller plan.&lt;/p&gt;

&lt;p&gt;Goal: Achieve cost savings while keeping the application online.&lt;/p&gt;

&lt;h2&gt;
  
  
  📌 Project Context
&lt;/h2&gt;

&lt;p&gt;A client’s web application was running on an oversized AWS Lightsail instance. The workload was consistently under-utilizing CPU, memory, and storage resources, which meant &lt;strong&gt;unnecessary monthly costs&lt;/strong&gt;. The client requested a &lt;strong&gt;downgrade to a smaller Lightsail instance&lt;/strong&gt; without affecting uptime, performance, or security.&lt;/p&gt;

&lt;p&gt;As a &lt;strong&gt;Cloud &amp;amp; DevOps Engineer&lt;/strong&gt;, I took ownership of the entire migration and cost-optimization project, delivering results in &lt;strong&gt;3 days&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Objectives
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Downgrade AWS Lightsail&lt;/strong&gt; to a smaller instance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ensure zero downtime&lt;/strong&gt; during migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Maintain SSL, Nginx, and PM2 configurations&lt;/strong&gt; for seamless performance.&lt;/li&gt;
&lt;li&gt;Implement &lt;strong&gt;rollback and backup strategies&lt;/strong&gt; to avoid risk.&lt;/li&gt;
&lt;li&gt;Document the entire process for transparency and client knowledge.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  📍 End-to-End Execution
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Day 1: Assessment &amp;amp; Planning&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Assessment &amp;amp; Planning&lt;br&gt;
Reviewed resource utilization metrics (CPU, RAM, Disk) via Lightsail Monitoring and CloudWatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Resource Utilization Analysis&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Logged into AWS Lightsail Dashboard.&lt;/li&gt;
&lt;li&gt;Checked &lt;strong&gt;CPU, RAM, and Network metrics&lt;/strong&gt; via built-in monitoring.&lt;/li&gt;
&lt;li&gt;Cross-verified with &lt;strong&gt;CloudWatch metrics&lt;/strong&gt; for accurate trend analysis.&lt;/li&gt;
&lt;li&gt;Identified that the current instance size was over-provisioned (using ~30% capacity).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Downgrade Strategy Design&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Determined the &lt;strong&gt;target instance size&lt;/strong&gt; that could handle peak loads.&lt;/li&gt;
&lt;li&gt;Outlined a &lt;strong&gt;rollback plan&lt;/strong&gt; using snapshots.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Created a &lt;strong&gt;migration checklist&lt;/strong&gt; covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application files&lt;/li&gt;
&lt;li&gt;Database&lt;/li&gt;
&lt;li&gt;Nginx configuration&lt;/li&gt;
&lt;li&gt;SSL certificates&lt;/li&gt;
&lt;li&gt;Environment variables&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Backup Creation&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Took a &lt;strong&gt;snapshot&lt;/strong&gt; of the existing Lightsail instance for disaster recovery.&lt;/li&gt;
&lt;li&gt;Exported database dump for redundancy using &lt;code&gt;mysqldump&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🎯 What I achieved on Day 1:
&lt;/h1&gt;

&lt;p&gt;Identified the current instance type and determined a smaller instance plan suitable for the workload.&lt;/p&gt;

&lt;p&gt;Created a rollback plan in case of failure.&lt;/p&gt;

&lt;p&gt;Backed up the existing instance snapshot for disaster recovery.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Day 2: Environment Setup &amp;amp; Migration&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Provisioning the New Lightsail Instance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Launched a new Lightsail instance with the &lt;strong&gt;downgraded size&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Configured &lt;strong&gt;SSH keys&lt;/strong&gt; for secure access.&lt;/li&gt;
&lt;li&gt;Set up networking (firewall rules, static IP).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Application Environment Configuration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Installed required packages:
&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="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 upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;nginx mysql-client nodejs npm &lt;span class="nt"&gt;-y&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; pm2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Pulled application files from transferred via SCP.&lt;/li&gt;
&lt;li&gt;Restored environment variables from &lt;code&gt;.env&lt;/code&gt; file.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Database Migration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created a new MySQL instance inside Lightsail (or reused existing).&lt;/li&gt;
&lt;li&gt;Imported the database:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  mysql &lt;span class="nt"&gt;-u&lt;/span&gt; username &lt;span class="nt"&gt;-p&lt;/span&gt; database_name &amp;lt; backup.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Nginx &amp;amp; SSL Setup&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configured Nginx virtual host with HTTPS redirect.&lt;/li&gt;
&lt;li&gt;Installed Let’s Encrypt SSL certificate with Certbot:
&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="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;certbot python3-certbot-nginx &lt;span class="nt"&gt;-y&lt;/span&gt;
  &lt;span class="nb"&gt;sudo &lt;/span&gt;certbot &lt;span class="nt"&gt;--nginx&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; example.com &lt;span class="nt"&gt;-d&lt;/span&gt; www.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Validated with &lt;code&gt;nginx -t&lt;/code&gt; and reloaded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;5. Application Process Management&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Started application with PM2:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;  pm2 start app.js &lt;span class="nt"&gt;--name&lt;/span&gt; myapp
  pm2 save
  pm2 startup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Ensured &lt;strong&gt;auto-start on reboot&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  🎯 What I achieved on Day 2 – Environment Preparation:
&lt;/h1&gt;

&lt;p&gt;Provisioned a new Lightsail instance with the downgraded size.&lt;/p&gt;

&lt;p&gt;Configured security groups, firewall rules, and SSH access.&lt;/p&gt;

&lt;p&gt;Migrated application files and environment variables from the old server.&lt;/p&gt;

&lt;p&gt;Installed and configured necessary dependencies (Nginx, Node.js, PM2, etc.).&lt;/p&gt;

&lt;p&gt;Validated that all services ran as expected on the smaller instance.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Day 3: DNS Cutover &amp;amp; Validation&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. DNS Redirection&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Updated Lightsail static IP for the new instance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Testing &amp;amp; Monitoring&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Verified that HTTPS was redirecting correctly.&lt;/li&gt;
&lt;li&gt;Tested login/admin pages to ensure proper routing.&lt;/li&gt;
&lt;li&gt;Confirmed API endpoints (&lt;code&gt;devapi.flexdelivery24.com&lt;/code&gt;) were functional.&lt;/li&gt;
&lt;li&gt;Monitored logs with:
&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="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/access.log
  &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/nginx/error.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Monitored CPU &amp;amp; RAM usage under live traffic for 24 hours.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Final Optimization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cleaned up unused packages and logs.&lt;/li&gt;
&lt;li&gt;Documented final server architecture and cost savings.&lt;/li&gt;
&lt;li&gt;Shared final access and rollback documentation with the client.&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  🎯 What I achieved on Day 3 – Cutover &amp;amp; Optimization
&lt;/h1&gt;

&lt;p&gt;Redirected DNS records to the new Lightsail instance.&lt;/p&gt;

&lt;p&gt;Tested application endpoints, admin login pages, and SSL configuration.&lt;/p&gt;

&lt;p&gt;Verified user sessions and ensured HTTPS redirection was functioning properly.&lt;/p&gt;

&lt;p&gt;Monitored server health (CPU/RAM) under production traffic for stability.&lt;/p&gt;

&lt;p&gt;Documented the final architecture and cost reduction achieved.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Deliverables
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;New &lt;strong&gt;Lightsail downgraded instance&lt;/strong&gt; in production.&lt;/li&gt;
&lt;li&gt;Full &lt;strong&gt;rollback snapshot&lt;/strong&gt; in case of failure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SSL-secured environment&lt;/strong&gt; with automated renewals.&lt;/li&gt;
&lt;li&gt;Updated &lt;strong&gt;DNS configuration&lt;/strong&gt; with zero downtime.&lt;/li&gt;
&lt;li&gt;Detailed &lt;strong&gt;post-migration documentation&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📊 Results &amp;amp; Impact
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost Reduction:&lt;/strong&gt; ~40% lower monthly Lightsail bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero Downtime:&lt;/strong&gt; Application stayed online throughout the migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Performance Stability:&lt;/strong&gt; New instance running at ~65% capacity under peak load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Client Satisfaction:&lt;/strong&gt; A leaner, secure, and efficient environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🏆 Impact&lt;br&gt;
This project reinforced my ability to balance cost optimization and reliability. With a careful step-by-step approach, I delivered results that aligned with business goals while maintaining system uptime.&lt;/p&gt;




&lt;h2&gt;
  
  
  💡 Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Always &lt;strong&gt;benchmark workloads&lt;/strong&gt; before downsizing to prevent under-provisioning.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Snapshots and database dumps&lt;/strong&gt; are critical for safe rollbacks.&lt;/li&gt;
&lt;li&gt;DNS cutover timing is crucial—setting a low TTL can make transitions smoother.&lt;/li&gt;
&lt;li&gt;Post-migration monitoring ensures the environment remains stable under real traffic.&lt;/li&gt;
&lt;/ol&gt;




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

&lt;p&gt;This project demonstrates my ability to manage &lt;strong&gt;cloud cost optimization&lt;/strong&gt; while balancing &lt;strong&gt;availability, security, and scalability&lt;/strong&gt;. Over 3 days, I delivered a solution that not only reduced costs but also improved infrastructure efficiency.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>awslightsail</category>
      <category>node</category>
      <category>mysql</category>
    </item>
    <item>
      <title>🚀 How I Deployed a Python Flask App with Docker and Troubleshot My Way to Victory 🛠️🐍🐳</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Tue, 13 May 2025 18:52:59 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/how-i-deployed-a-python-flask-app-with-docker-and-troubleshot-my-way-to-victory-1io5</link>
      <guid>https://dev.to/jamiu_cloud/how-i-deployed-a-python-flask-app-with-docker-and-troubleshot-my-way-to-victory-1io5</guid>
      <description>&lt;p&gt;Deploying a Python Flask app with Docker sounds straightforward… until it &lt;em&gt;isn’t&lt;/em&gt;. 😅 Recently, I rolled up my sleeves to containerize and deploy a simple Flask app connected to PostgreSQL. While it seemed like a walk in the cloud, I ran into several walls—but breaking through them made the win even sweeter. Here’s how it all went down 👇&lt;/p&gt;




&lt;h2&gt;
  
  
  🏗️ Project Setup
&lt;/h2&gt;

&lt;p&gt;I started with a basic Flask app that connects to a PostgreSQL database. My directory looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;📁 python-docker-project/
├── Dockerfile
├── compose.yml
├── app.py
├── requirements.txt
└── README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;My &lt;code&gt;app.py&lt;/code&gt; had two routes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/&lt;/code&gt;: returns a simple greeting&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/db&lt;/code&gt;: connects to PostgreSQL and returns the DB version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal? Run this app in a Docker container and use Docker Compose to spin up both the app and a PostgreSQL service in one command.&lt;/p&gt;




&lt;h2&gt;
  
  
  🐳 Dockerfile Setup
&lt;/h2&gt;

&lt;p&gt;I created a simple Dockerfile:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;

&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; 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;-r&lt;/span&gt; requirements.txt

&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&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;Looks good, right? 🤓 But here’s where the trouble started…&lt;/p&gt;




&lt;h2&gt;
  
  
  💥 The First Crash: Dependency Drama
&lt;/h2&gt;

&lt;p&gt;When I ran:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;Boom 💣&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ERROR: Could not find a version that satisfies the requirement &lt;span class="nv"&gt;click&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;8.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hmm. Turns out the &lt;code&gt;click==8.2.0&lt;/code&gt; version in &lt;code&gt;requirements.txt&lt;/code&gt; requires Python &amp;gt;= 3.10, but I was using &lt;code&gt;python:3.9-slim-buster&lt;/code&gt; as my base image.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔧 Fix:
&lt;/h3&gt;

&lt;p&gt;I upgraded the base image in my Dockerfile to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3.11-slim&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Problem solved. ✅&lt;/p&gt;




&lt;h2&gt;
  
  
  🧊 The Second Freeze: Compose Conundrum
&lt;/h2&gt;

&lt;p&gt;Feeling confident, I re-ran the build and... 💥 another error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;KeyError: &lt;span class="s1"&gt;'ContainerConfig'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, I’m like, &lt;em&gt;“What even is ContainerConfig?”&lt;/em&gt; 😤&lt;/p&gt;

&lt;h3&gt;
  
  
  🕵️‍♂️ Root Cause:
&lt;/h3&gt;

&lt;p&gt;I was using Docker Compose v1.29.2 (legacy), which doesn't play nice with newer images or Docker Engine versions.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔧 Fix:
&lt;/h3&gt;

&lt;p&gt;I upgraded to Docker Compose v2 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="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.docker/cli-plugins/
curl &lt;span class="nt"&gt;-SL&lt;/span&gt; https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; ~/.docker/cli-plugins/docker-compose
&lt;span class="nb"&gt;chmod&lt;/span&gt; +x ~/.docker/cli-plugins/docker-compose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And voilà! &lt;code&gt;docker compose up&lt;/code&gt; started working without the dreaded &lt;code&gt;ContainerConfig&lt;/code&gt; error. 🎉&lt;/p&gt;




&lt;h2&gt;
  
  
  🟢 Final Compose File
&lt;/h2&gt;

&lt;p&gt;Here’s a sneak peek at my &lt;code&gt;compose.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3.8'&lt;/span&gt;

&lt;span class="na"&gt;services&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="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5000:5000"&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;DATABASE_URL=postgresql://postgres:mysecretpassword@db:5432/mydatabase&lt;/span&gt;
    &lt;span class="na"&gt;depends_on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;db&lt;/span&gt;

  &lt;span class="na"&gt;db&lt;/span&gt;&lt;span class="pi"&gt;:&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;postgres:13&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mysecretpassword&lt;/span&gt;
      &lt;span class="na"&gt;POSTGRES_DB&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mydatabase&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="s"&gt;db-data:/var/lib/postgresql/data&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;db-data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  🎯 The Sweet Success
&lt;/h2&gt;

&lt;p&gt;After all the bumps, running the final command:&lt;br&gt;
&lt;/p&gt;

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

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpd0bd0pbp77cylrawh52.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%2Fpd0bd0pbp77cylrawh52.png" alt=" " width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;…worked flawlessly. I visited:&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%2F3zqnj0zdy4df7yyu36q6.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%2F3zqnj0zdy4df7yyu36q6.png" alt=" " width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http://localhost:5000/&lt;/code&gt; → Hello from Flask ✅&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%2Fhq4t5yw1drt8ri711xng.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%2Fhq4t5yw1drt8ri711xng.png" alt=" " width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;http://localhost:5000/db&lt;/code&gt; → PostgreSQL version info ✅&lt;/li&gt;
&lt;/ul&gt;




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

&lt;ul&gt;
&lt;li&gt;🐍 Match your Python version with compatible libraries (watch those &lt;code&gt;requirements.txt&lt;/code&gt; entries)&lt;/li&gt;
&lt;li&gt;🐳 Use Docker Compose v2 for smoother experience with modern images&lt;/li&gt;
&lt;li&gt;🧠 Don’t panic when you hit errors—each one teaches you something&lt;/li&gt;
&lt;li&gt;🔧 Always check if errors are due to version mismatches, especially with Docker&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🏁 Final Words
&lt;/h2&gt;

&lt;p&gt;This small project taught me that deployment isn't always plug-and-play—but that's what makes it rewarding. If you're running into Docker or Python-related issues, you're not alone. Just breathe, dig deep, and Google like a ninja. 💻&lt;/p&gt;




&lt;p&gt;Want more stories like this or need help with your own cloud projects? Follow me at &lt;strong&gt;CloudWithHorla&lt;/strong&gt; ☁️💪&lt;/p&gt;

</description>
      <category>aws</category>
      <category>docker</category>
      <category>python</category>
      <category>pythonflask</category>
    </item>
    <item>
      <title>🛠️ From Concept to Container: My Journey Deploying MongoDB &amp; Mongo-Express with Docker (No Docker Compose!)</title>
      <dc:creator>BAKRE JAMIU</dc:creator>
      <pubDate>Mon, 05 May 2025 19:50:58 +0000</pubDate>
      <link>https://dev.to/jamiu_cloud/from-concept-to-container-my-journey-deploying-mongodb-mongo-express-with-docker-no-docker-3ec5</link>
      <guid>https://dev.to/jamiu_cloud/from-concept-to-container-my-journey-deploying-mongodb-mongo-express-with-docker-no-docker-3ec5</guid>
      <description>&lt;h3&gt;
  
  
  🔥 Real-World Skills, Real Results — A Cloud Engineer’s Perspective
&lt;/h3&gt;

&lt;p&gt;In cloud engineering, we often celebrate smooth deployments—but the real growth lies in the challenges we troubleshoot along the way. This project is a perfect example of that. I set out to containerize and connect a backend database (&lt;strong&gt;MongoDB&lt;/strong&gt;) and a frontend admin interface (&lt;strong&gt;Mongo Express&lt;/strong&gt;) using Docker. The twist? I chose not to use Docker Compose. I wanted to master every part of the networking, environment configuration, and volume persistence manually—because that’s how pros sharpen their edge.&lt;/p&gt;

&lt;p&gt;Let’s dive into the steps I followed, the problems I solved, and how I ensured my data persisted—even after destroying and recreating my containers.&lt;/p&gt;




&lt;h3&gt;
  
  
  📌 Step 1: Create a Docker Network
&lt;/h3&gt;

&lt;p&gt;First, I created a custom bridge network to allow seamless communication between my containers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker network create jamiu-network
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  🧱 Step 2: Deploy the MongoDB Container
&lt;/h3&gt;

&lt;p&gt;Here, I launched MongoDB with a mounted volume to persist data. This step ensures that even if the container is deleted, the data will remain intact.&lt;br&gt;
&lt;/p&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;-d&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; mongo-database &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; jamiu-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 27017:27017 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; mongo-data:/data/db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongoadmin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret &lt;span class="se"&gt;\&lt;/span&gt;
  mongo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Takeaway:&lt;/strong&gt;&lt;br&gt;
Notice the &lt;code&gt;-v mongo-data:/data/db&lt;/code&gt; line—that’s the magic of Docker volumes doing the heavy lifting for persistence.&lt;/p&gt;


&lt;h3&gt;
  
  
  🌐 Step 3: Deploy Mongo Express
&lt;/h3&gt;

&lt;p&gt;Now, I deployed Mongo Express and connected it to my MongoDB container through the custom network. This acts as a simple frontend for interacting with my database.&lt;br&gt;
&lt;/p&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;-it&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;--network&lt;/span&gt; jamiu-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; mongo-express &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 8085:8081 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ME_CONFIG_OPTIONS_EDITORTHEME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ambiance"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ME_CONFIG_MONGODB_SERVER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mongo-database"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ME_CONFIG_MONGODB_ADMINUSERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"mongoadmin"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ME_CONFIG_MONGODB_ADMINPASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"secret"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ME_CONFIG_BASICAUTH_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"user"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;ME_CONFIG_BASICAUTH_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"fairly long password"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  mongo-express
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Access Mongo Express at:&lt;br&gt;
🌍 &lt;strong&gt;&lt;a href="http://localhost:8085" rel="noopener noreferrer"&gt;http://localhost:8085&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyr8a8d3grcjl08epuqge.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%2Fyr8a8d3grcjl08epuqge.png" alt=" " width="800" height="427"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  The view for Mongo-express
&lt;/h2&gt;
&lt;h3&gt;
  
  
  ⚔️ Real Talk: Troubleshooting Was My Superpower
&lt;/h3&gt;

&lt;p&gt;Was it smooth sailing? Absolutely not.&lt;/p&gt;

&lt;p&gt;Mongo Express initially failed to connect. The logs screamed:&lt;br&gt;
&lt;code&gt;Name does not resolve&lt;/code&gt; and &lt;code&gt;mongo:27017 Invalid argument&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The issue? I had incorrectly set the MongoDB hostname. Instead of using &lt;code&gt;"mongo-database"&lt;/code&gt;—which is the container name and acts as the hostname inside the Docker network—I had used &lt;code&gt;"mongo"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Once I corrected the &lt;code&gt;ME_CONFIG_MONGODB_SERVER&lt;/code&gt; value to match the actual MongoDB container name, everything clicked.&lt;/p&gt;

&lt;p&gt;🧠 &lt;strong&gt;Lesson:&lt;/strong&gt; Troubleshooting isn't a skill—it's a &lt;em&gt;superpower&lt;/em&gt;. It’s what separates a "deployer" from a real &lt;strong&gt;Cloud Engineer&lt;/strong&gt;.&lt;/p&gt;


&lt;h3&gt;
  
  
  🧪 Step 4: Test Volume Persistence Like a Pro
&lt;/h3&gt;

&lt;p&gt;After confirming the Mongo Express interface was working, I connected to the Mongo shell inside the container and added some test data:&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; &lt;span class="nt"&gt;-it&lt;/span&gt; mongo-database mongosh &lt;span class="nt"&gt;-u&lt;/span&gt; mongoadmin &lt;span class="nt"&gt;-p&lt;/span&gt; secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside Mongo shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;testdb&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testcollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;insertOne&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Bakre&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;skill&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Cloud Engineering&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testcollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came the real test.&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 the running container&lt;/span&gt;
docker &lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; mongo-database

&lt;span class="c"&gt;# Recreate it with the same volume&lt;/span&gt;
docker run &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; mongo-database &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--network&lt;/span&gt; jamiu-network &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-p&lt;/span&gt; 27017:27017 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-v&lt;/span&gt; mongo-data:/data/db &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGO_INITDB_ROOT_USERNAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongoadmin &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nv"&gt;MONGO_INITDB_ROOT_PASSWORD&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;secret &lt;span class="se"&gt;\&lt;/span&gt;
  mongo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Connected again...&lt;br&gt;
And guess what? 🎉&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;testcollection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The data was still there. My volume was working perfectly. &lt;strong&gt;Data persisted!&lt;/strong&gt;&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%2Fwb1zgwjrj1zzqb67i2ru.png" alt=" " width="800" height="492"&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🚀 Final Thoughts: This Is Cloud Engineering
&lt;/h3&gt;

&lt;p&gt;This project was more than deploying containers. It was about thinking like a professional Cloud Engineer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Building infrastructure with intention&lt;/li&gt;
&lt;li&gt;Debugging under pressure&lt;/li&gt;
&lt;li&gt;Validating persistence&lt;/li&gt;
&lt;li&gt;Embracing the long road for deeper mastery&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every Founder, CTO, and DevOps team needs people who don’t just know tools, but understand &lt;em&gt;systems&lt;/em&gt;. If you're looking for someone who’s not afraid of broken logs, bad connections, or late-night debugging—I’m your guy.&lt;/p&gt;

&lt;p&gt;🔧 &lt;strong&gt;Cloud isn’t just my skillset. It’s my mindset.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let’s connect.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>docker</category>
      <category>mongodb</category>
      <category>mongoexpress</category>
    </item>
  </channel>
</rss>
