Deploy Your First App to Kubernetes

Provision an EKS cluster with Terraform and deploy a containerized app on AWS.

Introduction

30 Second Summary

Every app you use needs to run somewhere. When millions of people open the same app at once, the system behind it has to handle the spike without crashing. Modern teams solve this with containers and orchestration tools that automatically scale up, recover from failures, and keep things running smoothly.

In this project, you will write Infrastructure as Code that creates a Kubernetes cluster on AWS, deploy a containerized web application to it, and experience scaling and self-healing in action.

What You'll Build

A fully automated Kubernetes infrastructure on AWS provisioned with Terraform, running a containerized Nginx web application accessible from your browser via a load balancer URL.

By the end of this project, you'll have:

  • A Terraform project that provisions a complete EKS cluster with networking, IAM roles, and managed worker nodes.
  • A running Nginx deployment accessible from the internet via a LoadBalancer Service URL.
  • Hands-on experience with kubectl commands to scale your application, inspect pods, and observe Kubernetes self-healing.
  • Secret Mission: Deploy a custom multi-container application with a ConfigMap and Horizontal Pod Autoscaler.

Are there any prerequisites?

You need an AWS account with administrator access. Basic familiarity with the command line (navigating folders and running commands) is helpful but not required.

This project provisions real AWS resources that cost money. New AWS accounts receive up to $200 in free credits, which more than covers this project. You will tear down all resources at the end to stop charges.

Install Your Tools

To provision a Kubernetes cluster on AWS using Terraform, you need three command-line tools installed on your machine.

Each tool handles a different part of the workflow. Terraform writes and applies your infrastructure code. The AWS CLI authenticates you with your AWS account. And kubectl lets you communicate with your Kubernetes cluster once it exists.

In this step, get ready to:
  • Install Terraform and verify it on your PATH.
  • Install the AWS CLI and configure your credentials.
  • Install kubectl and verify it on your PATH.
Install Terraform

Terraform is HashiCorp's Infrastructure as Code tool. It reads configuration files you write and translates them into API calls that create real cloud resources.

  • Open your browser and navigate to the Terraform install page:

https://developer.hashicorp.com/terraform/install

  • Under the Windows section, click the AMD64 binary download link to download the zip file.
  • Extract the zip file. Inside you will find a single file called terraform.exe.
  • Move terraform.exe to a permanent folder, for example C:\terraform.
  • Add that folder to your system PATH by pressing the Windows key, searching for Environment Variables, and clicking Edit the system environment variables.
  • In the System Properties window, click Environment Variables.
  • Under System variables, select Path and click Edit.
  • Click New and add C:\terraform (or wherever you placed the file).
  • Click OK on all open dialogs to save.

Now open a new PowerShell window and verify the installation. Check your version by running:

terraform --version

✔️ I see version 1.5.7 or higher

Terraform is installed and ready to use. Move on to the next substep.

ⓧ I see an older version

Your Terraform version is too old for this project. The modules we use require version 1.5.7 or higher.

  • Download the latest version from the same install page linked above.
  • Replace the old terraform.exe in your PATH folder with the new one.
  • Close and reopen PowerShell, then run terraform --version again.

ⓧ Command not found

PowerShell cannot find terraform. This means the folder containing terraform.exe is not in your PATH.

  • Confirm that terraform.exe exists in the folder you chose (e.g., C:\terraform).
  • Re-check your PATH environment variable and make sure the folder path is listed.
  • Close and reopen PowerShell after making PATH changes, then try again.

What is Terraform?

Terraform is an Infrastructure as Code tool. Instead of clicking through the AWS console to create resources, you describe what you want in configuration files. Terraform then figures out the order to create everything and makes the API calls for you.

This means your entire infrastructure becomes a set of version-controlled files you can share, review, and recreate at any time.

Install and configure the AWS CLI

The AWS CLI lets you interact with AWS services from your terminal. Terraform uses the credentials you configure here to authenticate with your AWS account.

  • Download the AWS CLI v2 MSI installer for Windows from this link:

https://awscli.amazonaws.com/AWSCLIV2.msi

  • Run the downloaded AWSCLIV2.msi file. The installer wizard will open.
  • Accept the license agreement and click Next through each screen, keeping the defaults.
  • Click Install, then Finish when the installation completes.

Close and reopen PowerShell, then verify the installation by running:

aws --version

✔️ I see aws-cli/2.x.x

AWS CLI v2 is installed. Continue to the credential configuration below.

ⓧ I see aws-cli/1.x.x

You have an older major version installed. AWS CLI v1 is missing features this project needs.

  • Uninstall AWS CLI v1 from Settings > Apps > Installed apps. Search for AWS CLI and uninstall it.
  • Download and run the v2 MSI installer from the link above.
  • Close and reopen PowerShell, then run aws --version again to confirm v2.

ⓧ Command not found

The installer may not have completed, or PowerShell was not restarted.

  • Close and reopen PowerShell to refresh your PATH.
  • If it still fails, re-run the AWSCLIV2.msi installer from the link above.
  • Make sure you have admin rights on your machine.

Now configure the CLI with your AWS credentials. You will need your Access Key ID and Secret Access Key from your AWS account.

Where do I find my AWS access keys?

In the AWS Management Console, click your account name in the top-right corner, then select Security credentials. Scroll to the Access keys section and click Create access key.

Copy both the Access Key ID and Secret Access Key. You will only see the secret key once, so save it somewhere safe.

  • Configure the AWS CLI by running:
aws configure

The command will prompt you for four values. Enter them one at a time:

  • For AWS Access Key ID, paste your Access Key ID.
  • For AWS Secret Access Key, paste your Secret Access Key.
  • For Default region name, type us-east-1.
  • For Default output format, press Enter to accept the default (none).

What does aws configure do?

This command saves your credentials to a local file at ~/.aws/credentials. Terraform reads these credentials automatically when it needs to create AWS resources. The region setting tells AWS which data center to use by default.

Seeing an error during aws configure?

Make sure you are pasting the full Access Key ID (starts with AKIA) and the full Secret Access Key. If you lost the secret key, create a new access key pair in the AWS console.

help me troubleshoot my AWS CLI configuration

Install kubectl

kubectl is the command-line tool for communicating with Kubernetes clusters. Once your EKS cluster is running, you will use kubectl to deploy applications and inspect your containers.

  • Download the kubectl binary for Windows by running this command in PowerShell:
curl.exe -LO "https://dl.k8s.io/release/v1.36.0/bin/windows/amd64/kubectl.exe"
  • Create a folder for kubectl, for example C:\kubectl.
  • Move the downloaded kubectl.exe file into that folder.
  • Add C:\kubectl to your system PATH using the same Environment Variables steps you used for Terraform.
  • Close and reopen PowerShell.

Verify kubectl is installed by running:

kubectl version --client

✔️ I see a client version number

kubectl is installed and ready. You will connect it to your EKS cluster in a later step.

ⓧ Command not found

PowerShell cannot find kubectl. The folder containing kubectl.exe is not in your PATH.

  • Confirm that kubectl.exe exists in the folder you chose (e.g., C:\kubectl).
  • Re-check your PATH environment variable and make sure the folder path is listed.
  • Close and reopen PowerShell after making PATH changes, then try again.

What is kubectl?

kubectl sends commands to the Kubernetes API server. Every action you take on a cluster (deploying apps, checking status, scaling up) goes through kubectl. Think of it as the remote control for your cluster.

Docker Desktop conflict?

If you have Docker Desktop installed, it bundles its own version of kubectl. You may need to make sure your PATH entry for C:\kubectl appears before the Docker Desktop entry so you use the version you just downloaded.

help me fix my kubectl PATH conflict with Docker Desktop

All three tools are ready. Next up, you will write the Terraform code that defines your entire Kubernetes infrastructure.

Write the Terraform Infrastructure Code

Your tools are installed and your AWS credentials are configured. Now it's time to write the actual infrastructure code that will create your Kubernetes cluster on AWS.

Instead of clicking through the AWS console to set up networking, security, and compute resources manually, you will define everything in Terraform configuration files. This means your entire infrastructure is version-controlled, repeatable, and can be torn down with a single command.

In this step, get ready to:
  • Create a project directory and initialize a Git repository.
  • Write Terraform configuration for VPC networking.
  • Write Terraform configuration for the EKS cluster and outputs.
Create your project directory and provider configuration

Every Infrastructure as Code project starts with a directory and version control. You will create a folder for your Terraform files and track changes with Git.

  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder, move into it, and initialize a Git repository by running these commands:
mkdir eks-terraform
cd eks-terraform
git init
  • Confirm the folder was initialized by running:
ls -a

You should see a .git directory in the output. This means Git is tracking your project.

Don't see .git?

Make sure you ran git init from inside the eks-terraform folder. Run pwd to confirm your current location ends with eks-terraform.

help me troubleshoot why git init didn't create a .git directory in my project folder

  • Open VS Code by pressing Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • In VS Code, click File in the top menu bar.
  • Click Open Folder.
  • Navigate to your Desktop, select the eks-terraform folder, then click Open.

Now you will create the first two Terraform files: one that declares which providers your project needs, and one that defines configurable variables.

  • In the VS Code Explorer sidebar, click the New File icon next to EKS-TERRAFORM.
  • Name the file versions.tf and press Enter.
  • Paste the following code into versions.tf:
terraform {
  required_version = ">= 1.5.7"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 6.28"
    }
  }
}

provider "aws" {
  region = var.region
}

What does this code do?

  • required_version = ">= 1.5.7" ensures anyone running this project uses a compatible Terraform version.
  • required_providers tells Terraform to download the AWS provider plugin from HashiCorp's registry.
  • provider "aws" configures the AWS provider to use a region variable (which you will define next).
  • Save versions.tf by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Create another new file in the Explorer sidebar and name it variables.tf.
  • Paste the following code into variables.tf:
variable "region" {
  description = "AWS region to deploy resources"
  type        = string
  default     = "us-east-1"
}

variable "cluster_name" {
  description = "Name of the EKS cluster"
  type        = string
  default     = "my-eks-cluster"
}

What does this code do?

  • variable "region" defines which AWS region to deploy into. The default is us-east-1 (the same region you configured the AWS CLI with).
  • variable "cluster_name" sets the name for your EKS cluster. You will reference this variable throughout the other files.
  • Save variables.tf.

You should now see both versions.tf and variables.tf listed in the VS Code Explorer sidebar.

✔️ Awesome, I've got everything!

Great. Make sure both files are saved before moving on.

ⓧ I'd like to double check the full code

Here is versions.tf:

terraform {
  required_version = ">= 1.5.7"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 6.28"
    }
  }
}

provider "aws" {
  region = var.region
}

Here is variables.tf:

variable "region" {
  description = "AWS region to deploy resources"
  type        = string
  default     = "us-east-1"
}

variable "cluster_name" {
  description = "Name of the EKS cluster"
  type        = string
  default     = "my-eks-cluster"
}
Define the VPC networking layer

Your EKS cluster needs a network to live in. A VPC (Virtual Private Cloud) provides isolated networking with public subnets for load balancers and private subnets for your worker nodes.

  • Create a new file in the Explorer sidebar and name it vpc.tf.
  • Paste the following code into vpc.tf:
data "aws_availability_zones" "available" {
  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "6.6.1"

  name = "${var.cluster_name}-vpc"
  cidr = "10.0.0.0/16"

  azs             = slice(data.aws_availability_zones.available.names, 0, 2)
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true

  public_subnet_tags = {
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
  }

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

What does this code do?

  • data "aws_availability_zones" looks up the available zones in your region so the VPC spans multiple physical data centers for reliability.
  • module "vpc" uses a community Terraform module that handles all the complex networking setup for you (route tables, internet gateway, NAT gateway).
  • private_subnets are where your worker nodes will run. They access the internet through the NAT gateway but are not directly reachable from outside.
  • public_subnets are where load balancers will be placed so they can receive traffic from the internet.
  • The kubernetes.io/role/elb tags tell Kubernetes which subnets to use when creating load balancers.

Why a single NAT gateway?

Production environments use one NAT gateway per availability zone for high availability. For this learning project, a single NAT gateway saves costs (each one costs approximately $0.045/hour) while still providing private subnet internet access.

  • Save vpc.tf.

You should see vpc.tf in the Explorer sidebar alongside your other two files.

✔️ Awesome, I've got everything!

Great. Make sure the file is saved before moving on.

ⓧ I'd like to double check the full code

Here is vpc.tf:

data "aws_availability_zones" "available" {
  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "6.6.1"

  name = "${var.cluster_name}-vpc"
  cidr = "10.0.0.0/16"

  azs             = slice(data.aws_availability_zones.available.names, 0, 2)
  private_subnets = ["10.0.1.0/24", "10.0.2.0/24"]
  public_subnets  = ["10.0.101.0/24", "10.0.102.0/24"]

  enable_nat_gateway   = true
  single_nat_gateway   = true
  enable_dns_hostnames = true

  public_subnet_tags = {
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
  }

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

Seeing red squiggles in VS Code?

VS Code may show syntax warnings for .tf files if you do not have the HashiCorp Terraform extension installed. The code will still work fine. You can optionally install the extension by searching for "HashiCorp Terraform" in the Extensions sidebar.

help me fix Terraform syntax highlighting issues in VS Code

Configure the EKS cluster and outputs

With the network defined, you can now declare the EKS cluster itself. This file tells AWS to create a managed Kubernetes control plane and a group of worker nodes that run your containers.

  • Create a new file in the Explorer sidebar and name it eks.tf.
  • Paste the following code into eks.tf:
module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.0"

  cluster_name    = var.cluster_name
  cluster_version = "1.33"

  endpoint_public_access = true

  enable_cluster_creator_admin_permissions = true

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    default = {
      ami_type       = "AL2023_x86_64_STANDARD"
      instance_types = ["t3.medium"]

      min_size     = 2
      max_size     = 3
      desired_size = 2
    }
  }

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

What does this code do?

  • module "eks" uses the official community module to handle the complex EKS setup (IAM roles, security groups, cluster configuration).
  • cluster_version = "1.33" sets the Kubernetes version your cluster will run.
  • enable_cluster_creator_admin_permissions = true grants your AWS user admin access to the cluster. Without this, you would not be able to run kubectl commands against it.
  • eks_managed_node_groups defines a group of 2 EC2 instances (t3.medium) that act as your Kubernetes worker nodes.
  • subnet_ids = module.vpc.private_subnets places the worker nodes in the private subnets you defined in vpc.tf.
  • Save eks.tf.
  • Create one more new file in the Explorer sidebar and name it outputs.tf.
  • Paste the following code into outputs.tf:
output "cluster_endpoint" {
  description = "Endpoint for EKS control plane"
  value       = module.eks.cluster_endpoint
}

output "cluster_name" {
  description = "Kubernetes Cluster Name"
  value       = module.eks.cluster_name
}

output "region" {
  description = "AWS region"
  value       = var.region
}

What does this code do?

Outputs are values that Terraform prints after a successful deployment. You will use cluster_endpoint and cluster_name later to configure kubectl to connect to your cluster.

  • Save outputs.tf.

Your Explorer sidebar should now show all five files: versions.tf, variables.tf, vpc.tf, eks.tf, and outputs.tf.

✔️ Awesome, I've got everything!

Great. Make sure all five files are saved before continuing.

ⓧ I'd like to double check the full code

Here is eks.tf:

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.0"

  cluster_name    = var.cluster_name
  cluster_version = "1.33"

  endpoint_public_access = true

  enable_cluster_creator_admin_permissions = true

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  eks_managed_node_groups = {
    default = {
      ami_type       = "AL2023_x86_64_STANDARD"
      instance_types = ["t3.medium"]

      min_size     = 2
      max_size     = 3
      desired_size = 2
    }
  }

  tags = {
    Terraform   = "true"
    Environment = "dev"
  }
}

Here is outputs.tf:

output "cluster_endpoint" {
  description = "Endpoint for EKS control plane"
  value       = module.eks.cluster_endpoint
}

output "cluster_name" {
  description = "Kubernetes Cluster Name"
  value       = module.eks.cluster_name
}

output "region" {
  description = "AWS region"
  value       = var.region
}

File not showing in Explorer?

Make sure you created the file inside the eks-terraform folder (not inside a subfolder or outside the project). The file should appear at the top level of the Explorer panel.

help me figure out why my Terraform file isn't showing in the VS Code Explorer sidebar

Finally, commit all your files to Git so you have a clean baseline before running Terraform.

  • Open the integrated terminal in VS Code by pressing Ctrl+` (backtick key, same shortcut on macOS and Windows).
  • Stage and commit all files by running these commands:
git add .
git commit -m "Add Terraform infrastructure code"

You should see output confirming that 5 files were committed.

Git asking for your name and email?

If Git prompts you to set your identity, run these two commands first (replacing the placeholder values with your own name and email):

git config user.name "Your Name"
git config user.email "your@email.com"

Then run git commit -m "Add Terraform infrastructure code" again.

help me configure Git user identity so I can make commits

Your entire infrastructure is now defined in code. Next up, you will run Terraform to provision all of these resources on AWS.

Provision the Infrastructure

You have all your Terraform configuration files written and ready to go.

Now it is time to turn that code into real AWS resources. Terraform follows a three-command workflow: init downloads dependencies, plan previews what will be created, and apply actually builds it. By the end of this step, you will have a fully running EKS cluster on AWS that you can connect to with kubectl.

In this step, get ready to:
  • Initialize Terraform and download provider plugins and modules.
  • Preview and apply the infrastructure to create 50+ AWS resources.
  • Connect kubectl to your new EKS cluster and verify your worker nodes.
Initialize Terraform and preview the plan

Before Terraform can create any resources, it needs to download the AWS provider plugin and the VPC and EKS modules you referenced in your configuration files.

  • Initialize your Terraform project by running this command:
terraform init

What does this command do?

Terraform reads your .tf files, identifies the required providers and modules, then downloads them into a hidden .terraform directory.

It also creates a .terraform.lock.hcl file that locks the exact versions used. This ensures everyone on a team gets identical provider versions.

You should see output ending with the message "Terraform has been successfully initialized!" along with a note about running terraform plan next.

Seeing an error during init?

If you see a credentials error, make sure your AWS CLI is configured correctly. Run aws sts get-caller-identity to confirm your credentials are working.

If you see a version constraint error, confirm your Terraform version is 1.5.7 or higher by running terraform --version.

Help me debug my terraform init error

Now that Terraform has its dependencies, you can preview exactly what it will create.

  • Preview the infrastructure plan by running:
terraform plan

What does terraform plan show?

The plan command compares your configuration to the current state (which is empty since nothing exists yet). It outputs a list of every resource it will create, including the VPC, subnets, route tables, internet gateway, NAT gateway, IAM roles, the EKS control plane, and worker nodes.

No changes are made to your AWS account at this stage. The plan is purely a preview.

You should see a summary at the bottom showing approximately 50+ resources to add, 0 to change, and 0 to destroy.

Plan showing errors?

If the plan shows a provider configuration error, double-check that your variables.tf has region set to us-east-1 and that your versions.tf provider block references var.region.

Help me fix my terraform plan error

Apply the infrastructure changes

The plan looks good. Now it is time to actually create those resources on AWS.

  • Apply the Terraform configuration by running:
terraform apply
  • Terraform will show the plan again and prompt you with "Do you want to perform these actions?" Type yes and press **Enter** to confirm.

This takes 10-15 minutes

The EKS cluster provisioning takes approximately 10-15 minutes. This is normal. Do not close your terminal or interrupt the process.

You will see progress messages like "Still creating..." as Terraform provisions each resource. The EKS control plane is the longest part of the build.

What is Terraform creating?

While you wait, here is what Terraform is building behind the scenes:

  • A VPC (Virtual Private Cloud) with a 10.0.0.0/16 CIDR block to isolate your network.
  • 2 public subnets and 2 private subnets spread across availability zones for redundancy.
  • An internet gateway for public subnet internet access, plus route tables to direct traffic.
  • A NAT gateway so your private subnets can reach the internet for updates without being publicly exposed.
  • The EKS control plane (the Kubernetes brain that manages your cluster).
  • IAM roles that grant the EKS cluster and worker nodes the permissions they need.
  • A managed node group of 2 t3.medium EC2 instances that run your containerized workloads.

Once complete, you should see a message like "Apply complete! Resources: X added, 0 changed, 0 destroyed." along with your output values (cluster endpoint, cluster name, and region).

Apply failed or timed out?

If you see an access denied error, your IAM user may not have administrator permissions. Verify your IAM user has the AdministratorAccess policy attached in the AWS console.

If the apply times out or fails partway through, run terraform apply again. Terraform tracks state and will pick up where it left off.

Help me fix my terraform apply error

Connect kubectl to your cluster

Your EKS cluster is live, but your local kubectl tool does not know how to reach it yet. You need to update your kubeconfig file so kubectl can authenticate and communicate with the cluster.

  • Configure kubectl to connect to your EKS cluster by running:
aws eks --region us-east-1 update-kubeconfig --name my-eks-cluster

What does this command do?

This command tells the AWS CLI to fetch the cluster connection details (endpoint, certificate, authentication token) and write them into your local ~/.kube/config file.

After this, every kubectl command you run will automatically target your my-eks-cluster cluster.

  • Verify your connection to the cluster by running:
kubectl get nodes

You should see 2 worker nodes listed with a STATUS of Ready. These are the t3.medium EC2 instances that Terraform provisioned as part of your managed node group.

Nodes not showing as Ready?

If kubectl get nodes returns an error about connecting to the server, make sure the update-kubeconfig command completed successfully. Try running it again.

If your nodes show a status other than Ready, wait 1-2 minutes and rerun kubectl get nodes. Nodes can take a moment to fully register with the cluster after provisioning.

Help me connect kubectl to my EKS cluster

Your Kubernetes cluster is live and connected. Next up, you will deploy an application onto those worker nodes and make it accessible from the internet.

Deploy Your First Application

Your EKS cluster is provisioned and your worker nodes are standing by. Now it's time to put something on them.

In Kubernetes, you don't run containers directly. You describe your desired state in YAML manifests, and Kubernetes makes it happen. In this step, you'll deploy an Nginx web server and expose it to the internet through a load balancer.

In this step, get ready to:
  • Create and apply a Deployment manifest to run Nginx containers.
  • Create and apply a Service manifest to expose your app with a load balancer.
  • Explore your running containers with kubectl commands.
Create and apply the Nginx Deployment

A Deployment tells Kubernetes how many copies of a container to run and how to keep them healthy. If a container crashes, the Deployment automatically replaces it.

  • In VS Code, create a new file called nginx-deployment.yaml in your project directory.
  • Paste the following YAML into the file:
# Kubernetes Deployment manifest for Nginx
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  # Run 2 identical copies of the container
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
        # Resource limits prevent one container from starving others
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 250m
            memory: 256Mi

What does this manifest do?

This manifest describes the desired state of your application to Kubernetes.

  • replicas: 2 tells Kubernetes to always keep 2 copies (called Pods) running.
  • image: nginx:1.27 specifies the exact container image to pull from Docker Hub.
  • The resources block sets CPU and memory guardrails so one container can't consume all node resources.
  • The selector and labels connect the Deployment to its Pods using the key app: nginx.
  • Save nginx-deployment.yaml.

✔️ Awesome, I've got everything!

Great. Make sure you've saved the file before moving on.

ⓧ I'd like to double check the full code

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.27
        ports:
        - containerPort: 80
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 250m
            memory: 256Mi
  • Apply the Deployment to your cluster by running this command:
kubectl apply -f nginx-deployment.yaml
  • You should see deployment.apps/nginx-deployment created in the output.
  • Verify your Pods are running by running:
kubectl get pods

You should see two pods with names starting with nginx-deployment- and a STATUS of Running. It may take 30-60 seconds for both pods to reach this state.

Pods stuck in Pending or ContainerCreating?

  • Wait 60 seconds and run kubectl get pods again. Pulling the Nginx image for the first time takes a moment.
  • If a pod shows ImagePullBackOff, check that you typed nginx:1.27 exactly in your YAML file.
  • Run kubectl describe pod followed by the pod name to see detailed error events.

Why aren't my Kubernetes pods starting?

Expose the Deployment with a LoadBalancer Service

Your Nginx pods are running, but they're only reachable inside the cluster. A Service of type LoadBalancer tells AWS to create a real load balancer that routes internet traffic to your pods.

  • Create a new file called nginx-service.yaml in your project directory.
  • Paste the following YAML into the file:
# Kubernetes Service manifest to expose Nginx to the internet
apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    app: nginx
spec:
  # LoadBalancer type provisions a real AWS load balancer
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80

What does this Service do?

The Service acts as a stable network endpoint for your pods.

  • type: LoadBalancer tells AWS to provision a load balancer with a public URL.
  • selector: app: nginx routes traffic only to pods with the matching label.
  • port: 80 is the external port. targetPort: 80 is the port inside the container.
  • Save nginx-service.yaml.

✔️ Awesome, I've got everything!

Great. Make sure you've saved the file before moving on.

ⓧ I'd like to double check the full code

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  labels:
    app: nginx
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
  - protocol: TCP
    port: 80
    targetPort: 80
  • Apply the Service to your cluster by running:
kubectl apply -f nginx-service.yaml
  • You should see service/nginx-service created in the output.
  • Watch for the external URL to appear by running:
kubectl get svc --watch

The EXTERNAL-IP column starts as <pending>. After 2-3 minutes, it changes to a long AWS hostname. This is your public URL.

  • Once you see the hostname appear, press Ctrl+C to stop the watch.
  • Copy the hostname from the EXTERNAL-IP column.

EXTERNAL-IP stays pending for more than 5 minutes?

  • Confirm your VPC public subnets have the tag kubernetes.io/role/elb = 1. Without this tag, AWS cannot provision the load balancer.
  • Check that your Security Group allows inbound traffic on port 80. Run kubectl describe svc nginx-service to see events for more detail.

Why is my LoadBalancer external IP stuck on pending?

Access and explore your running application

Your Nginx application is live on the internet. Time to visit it and poke around with kubectl commands.

  • Open your web browser and paste the EXTERNAL-IP hostname into the address bar.
  • Press Enter.

You should see the Nginx welcome page with the heading "Welcome to nginx!" displayed in your browser.

What just happened?

Your browser sent a request to the AWS load balancer. The load balancer forwarded it to one of your two Nginx pods. The pod served back the default welcome page.

This is the same pattern production apps use. Kubernetes distributes traffic across healthy pods automatically.

Now explore your containers with a few kubectl commands.

  • List your running pods by running:
kubectl get pods
  • Copy one of the pod names from the output (it looks like nginx-deployment-xxxxxxxxx-xxxxx).
  • Get detailed information about that pod by running:
kubectl describe pod [[POD_NAME="your pod name from the output above"]]

What does kubectl describe show?

The describe command shows everything about a pod: which node it's running on, its IP address, container status, resource limits, and a timeline of events. This is your go-to debugging tool when something goes wrong.

  • View the pod's access logs by running:
kubectl logs [[POD_NAME="your pod name from the output above"]]

You should see HTTP request entries from when you visited the page in your browser. Each line shows the request method, path, and response code.

Not seeing any log entries?

  • Make sure you are checking the logs of the same pod that served your request. Try the other pod name if the first one shows no entries.
  • Refresh the Nginx page in your browser, then run the kubectl logs command again.

How do I read Kubernetes pod logs with kubectl?

Your application is live on the internet and you know how to inspect it. Next up, you'll push it to its limits by scaling up, deleting pods, and watching Kubernetes self-heal in real time.

Scale and Observe Kubernetes in Action

Your Nginx application is live and accessible from the internet through a LoadBalancer URL. You have 2 replicas handling traffic across your EKS cluster.

But what happens when traffic spikes and you need more capacity? Or when a container crashes unexpectedly? In this step, you will put Kubernetes to the test by scaling your application up, watching it self-heal after a failure, and then scaling it back down.

In this step, get ready to:
  • Scale your deployment up to 5 replicas and watch them spin up.
  • Delete a pod and watch Kubernetes automatically replace it.
  • Scale back down and review your deployment history.
Scale up to 5 replicas

Right now your deployment runs 2 pods. Kubernetes lets you change that replica count on the fly without any downtime.

  • Scale your deployment to 5 replicas by running this command:
kubectl scale deployment nginx-deployment --replicas=5
  • Watch the new pods spin up in real time by running:
kubectl get pods --watch

You should see 5 pods listed. The 3 new pods transition from Pending to ContainerCreating to Running within a few seconds.

  • Press Ctrl+C to exit the watch mode once all 5 pods show Running.

What just happened?

You told Kubernetes your desired state is 5 replicas. Kubernetes compared that to the current state (2 replicas), calculated the difference, and created 3 new pods to close the gap.

This declarative model is what makes Kubernetes powerful. You declare what you want, and Kubernetes figures out how to make it happen.

Pods stuck in Pending?

Your node group has a max size of 3 nodes with t3.medium instances. If all 5 pods cannot fit on 2 nodes, EKS may scale up to a 3rd node. Wait a minute for the new node to become Ready.

If pods remain Pending for more than 2 minutes, check node capacity with kubectl describe nodes and look for resource pressure.

Why are my pods stuck in Pending after scaling?

Delete a pod and observe self-healing

Kubernetes constantly monitors your pods to ensure the actual state matches your desired state. If a pod disappears for any reason, Kubernetes replaces it automatically.

  • List your current pods to grab a pod name by running:
kubectl get pods

You should see 5 pods listed, each with a unique name like nginx-deployment-xxxxx-xxxxx. Copy one of the pod names from the NAME column.

  • Delete that pod to simulate a crash by running:
kubectl delete pod [[POD_NAME="one of your pod names from the output above"]]
  • Immediately check the pods again to see Kubernetes respond:
kubectl get pods

You should still see 5 pods. The pod you deleted is gone, and a brand-new pod has appeared in its place with a different name and a very young age.

What is self-healing?

Kubernetes runs a control loop that constantly compares the desired state (5 replicas) to the actual state. When you deleted a pod, the actual state dropped to 4. Kubernetes immediately created a replacement to bring it back to 5.

This is why containers crash in production without anyone waking up at 2 AM. Kubernetes handles the recovery automatically.

Still seeing only 4 pods?

The replacement pod takes a few seconds to start. Run kubectl get pods again after 5-10 seconds. If the new pod is stuck in ContainerCreating, wait a moment longer for the image to pull.

Help me understand why my replacement pod isn't appearing.

Scale back down and review deployment history

You have proven that Kubernetes can scale up and self-heal. Now scale the deployment back down to its original 2 replicas.

  • Scale your deployment back to 2 replicas by running:
kubectl scale deployment nginx-deployment --replicas=2
  • Confirm the scale-down by running:
kubectl get pods

You should see only 2 pods remaining. Kubernetes terminated the extra 3 pods gracefully.

  • Explore your deployment's rollout history by running:
kubectl rollout history deployment/nginx-deployment

You should see at least one revision listed. This history tracks every change to the deployment's pod template, which is useful for rolling back to a previous version if something goes wrong.

Why does rollout history matter?

In production, teams push new container images frequently. If a new version has a bug, you can roll back to a previous revision instantly with a single command. Kubernetes keeps a record of recent revisions so you always have a safety net.

Seeing 'No rollout history found'?

This can happen if the deployment was created from a manifest without any subsequent changes to the pod template. Scaling does not create new revisions. You should still see at least revision 1 from the initial creation.

Help me understand my rollout history output.

Secret mission

Deploy a Multi-Container App with ConfigMap and Autoscaling

You have a working Kubernetes deployment, but it is serving the default Nginx welcome page. In production, teams inject custom configuration into containers without rebuilding images. They also set up autoscaling so their apps handle traffic spikes automatically. Can you add a ConfigMap, mount it as a volume, and wire up a Horizontal Pod Autoscaler?

Clean Up Your Resources

Clean Up Your Resources

This project created AWS resources that cost real money while they run. Decide whether to keep your resources running or delete them entirely.

Cost warning

Your EKS cluster costs $0.10/hour for the control plane alone. Combined with the EC2 worker nodes (~$0.042/hour each), the NAT gateway (~$0.045/hour), and the Load Balancer (~$0.025/hour), the total runs approximately $5-7/day.

If you are not actively using this cluster, delete everything now to stop all charges. EKS clusters cannot be paused.

Resources you used:

  • EKS cluster (my-eks-cluster) with 2 t3.medium worker nodes.
  • VPC (my-eks-cluster-vpc) with public/private subnets, NAT gateway, and internet gateway.
  • Elastic Load Balancer created by the Kubernetes Service.
  • Kubernetes resources: Deployment, Service, ConfigMap, HPA, and Metrics Server.
  • Local project directory with Terraform files and YAML manifests.

Keep everything running

No action needed. Choose this if you are still actively building on this cluster or plan to continue within the next few hours.

  • Your cluster will continue running and incurring charges at approximately $5-7/day.
  • All Kubernetes resources (Deployment, Service, HPA) remain active.
  • Your LoadBalancer URL continues serving traffic.
  • Terraform, AWS CLI, and kubectl remain installed on your machine.

Pause - I'll come back to this later

EKS clusters cannot be paused or hibernated. There is no way to stop charges without deleting the cluster entirely.

If you want to come back to this project later, your best option is to delete everything now and re-provision later. Your Terraform code makes this easy.

  • Follow the steps in the Delete tab to tear down all AWS resources.
  • Keep your local project directory with the .tf and .yaml files intact.
  • When you are ready to resume, run terraform apply again to recreate the cluster in about 10-15 minutes.

Delete - I don't want to use this again

Remove all project resources completely. This stops all AWS charges and cleans up your local files.

  • First, delete the Kubernetes LoadBalancer Service to release the associated AWS Load Balancer cleanly. Run this command:
kubectl delete svc nginx-service
  • Wait about 30 seconds for the Load Balancer to be fully released.
  • Destroy all Terraform-managed infrastructure by running:
terraform destroy
  • When prompted, type yes and press Enter to confirm. This takes approximately 10 minutes.
  • Once complete, verify no resources remain by checking your AWS console or running:
aws eks list-clusters --region us-east-1
  • You should see an empty list with no clusters.
  • Delete your local project directory. On macOS/Linux:
cd ~/Desktop
rm -rf my-eks-project
  • On Windows (PowerShell):
cd ~\Desktop
Remove-Item -Recurse -Force my-eks-project
  • Remove the stale kubectl cluster configuration by running:
kubectl config delete-context arn:aws:eks:us-east-1:$(aws sts get-caller-identity --query Account --output text):cluster/my-eks-cluster

What does terraform destroy do?

This single command removes all 50+ AWS resources that Terraform created: the EKS cluster, managed node group, VPC, subnets, route tables, NAT gateway, internet gateway, security groups, and IAM roles. Everything defined in your .tf files gets torn down in reverse dependency order.

That's a Wrap!

That's a Wrap!

Nice work! You just deployed a real Kubernetes application on AWS using Infrastructure as Code.

You've learned how to:

  • Provisioned a complete VPC with public and private subnets and an EKS cluster with managed worker nodes using Terraform.
  • Deployed a containerized Nginx application and exposed it to the internet with a LoadBalancer Service.
  • Scaled the application up and down, witnessed Kubernetes self-healing, and tore down all infrastructure with a single terraform destroy.
  • Secret Mission: Deployed a custom app with ConfigMaps and a Horizontal Pod Autoscaler for automatic scaling based on CPU utilization.

Ready to quiz yourself?