Provision an EKS Cluster with Terraform
Build a production-style EKS cluster on AWS using Terraform modules.
Introduction
30 Second Summary
Setting up servers by hand is like building furniture without instructions. Infrastructure as Code solves this by letting you define your entire cloud setup in files you can version, review, and reuse.
In this project, you will use Terraform to provision a production-style Kubernetes cluster on AWS. You will write configuration that creates a dedicated network, spins up an EKS cluster with worker nodes, deploys a sample application, and tears everything down with a single command.
What You'll Build
When you finish, you will open your browser, paste a URL from your terminal, and see a live Nginx welcome page served from a Kubernetes cluster you built entirely with Terraform.
By the end of this project, you'll have:
- A running EKS cluster with 2 worker nodes, provisioned entirely through Terraform modules from files you wrote from scratch.
- A live Nginx welcome page in your browser, served from your cluster through a public URL you can share with anyone.
- A clean teardown with a single terraform destroy command, leaving zero billable resources behind.
- Secret Mission: an optional challenge to push your skills further.
Are there any prerequisites?
You will need an AWS account with the AWS CLI configured, Terraform installed, and kubectl installed. Step 1 will verify all three are ready.
Before We Start
Before we dive in, let's lock in what you're about to build and why it matters. This is your moment to commit to the goal before any hands-on work begins.
Set Up Your Terraform Project
Before you can provision anything on AWS, Terraform needs a project structure that tells it which cloud provider to talk to and which versions to lock down. Without these configuration files, Terraform has no context for what to build or where.
In this step, you'll create a project directory and write three foundational files. These files pin the AWS provider version, set the target region, and define reusable variables for your cluster.
In this step, get ready to:
- Create a project directory for your Terraform configuration.
- Write versions.tf, providers.tf, and variables.tf to configure the AWS provider and define cluster variables.
- Run terraform init to initialize the project and download the AWS provider.
Create the project directory and pin provider versions
Every Terraform project starts with a directory containing .tf files. The first file you need is versions.tf, which locks the Terraform version and the AWS provider version so your infrastructure builds consistently.
- Move to your Desktop by running this command:
cd ~/Desktop
- Create your project folder and move into it by running these commands:
mkdir eks-terraform
cd eks-terraform
- Open the eks-terraform folder in your code editor.
- Create a new file called versions.tf and add the following code:
terraform {
required_version = ">= 1.5.7"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
What does this code do?
- required_version = ">= 1.5.7" ensures anyone running this project has at least Terraform 1.5.7 installed. If their version is older, Terraform refuses to run.
- source = "hashicorp/aws" tells Terraform to download the official AWS provider from the Terraform Registry.
- version = "~> 6.0" pins the provider to any 6.x release. The ~> operator allows minor version updates but blocks major version jumps that could break your code.
- Save versions.tf.
- Confirm the file exists by running:
ls
You should see versions.tf in the output.
Configure the AWS provider
The provider block tells Terraform how to authenticate and which region to target. Your AWS CLI credentials are already configured, so the provider picks them up automatically.
- Create a new file called providers.tf in your code editor and add the following code:
provider "aws" {
region = var.region
}
What does this code do?
This configures the AWS provider to deploy resources in whatever region the region variable holds. You'll define that variable in the next file. Using var.region instead of a hardcoded value makes the project easy to redeploy in a different region later.
- Save providers.tf.
- Confirm both files exist by running:
ls
You should see providers.tf and versions.tf in the output.
Define variables and initialize Terraform
The last configuration file defines the variables that the provider and future modules will reference. Once all three files are in place, you can initialize the project.
- Create a new file called variables.tf and add the following code:
variable "cluster_name" {
description = "Name of the EKS cluster"
type = string
default = "nextwork-eks"
}
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
What does this code do?
- cluster_name sets the name of your EKS cluster to nextwork-eks by default. You'll use this variable throughout the project when naming resources.
- region defaults to us-east-1. This is the region your provider block references with var.region.
- Both variables have a default value, so Terraform won't prompt you to enter them every time you run a command.
- Save variables.tf.
✔️ Awesome, I've got everything!
Great. Double check you've saved all three files before running terraform init.
ⓧ 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.0"
}
}
}
Here is providers.tf:
provider "aws" {
region = var.region
}
Here is variables.tf:
variable "cluster_name" {
description = "Name of the EKS cluster"
type = string
default = "nextwork-eks"
}
variable "region" {
description = "AWS region"
type = string
default = "us-east-1"
}
Before you run the next command, what do you think Terraform will download based on the files you just wrote?
- Initialize your Terraform project by running:
terraform init
Terraform reads your versions.tf file, downloads the hashicorp/aws provider, and creates a .terraform directory to store it. You should see a message ending with:
Terraform has been successfully initialized!
What did terraform init just do?
- It downloaded the AWS provider plugin matching your ~> 6.0 version constraint and stored it in the .terraform directory.
- It created a .terraform.lock.hcl lock file that records the exact provider version it downloaded. This ensures everyone working on the project uses the same version.
Seeing an error instead?
- If you see a syntax error, open the file mentioned in the error message and compare it character-for-character against the code blocks above. A missing brace or extra quote is the most common cause.
- If you see a provider-related error, make sure source = "hashicorp/aws" in versions.tf is spelled exactly as shown.
- If you see a network timeout, check your internet connection and try running terraform init again.
Why is terraform init failing for me?
Your Terraform project is initialized and ready to provision real AWS resources. Next up, you'll use a community module to create an entire VPC in just a few lines of code.
Provision the VPC
Your Terraform project is initialized and ready to talk to AWS. The first piece of real infrastructure you need is a VPC, the virtual network where your EKS cluster will live.
EKS requires a VPC with properly tagged subnets to know where to place the control plane and worker nodes. Building this by hand means wiring up dozens of individual AWS resources. Instead, you will use the terraform-aws-modules/vpc/aws community module to create the entire networking foundation in a single module block.
In this step, get ready to:
- Create a vpc.tf file using the VPC community module with subnets and EKS tags.
- Create an outputs.tf file to expose the VPC ID.
- Run terraform apply and verify the VPC is created on AWS.
Create the VPC module configuration
The VPC module handles creating the VPC itself, public and private subnets, route tables, a NAT gateway, and an internet gateway. Without the module, you would need to define each of these as separate Terraform resources.
Why use community modules instead of raw resources?
The terraform-aws-modules/vpc/aws module encapsulates 20+ individual AWS resources (subnets, route tables, NAT gateways, internet gateways) into a single reusable block. Writing these from scratch would take much longer and teach plumbing rather than IaC patterns.
Community modules are how practitioners actually provision infrastructure in production. Learning to compose them is the skill that transfers to real jobs.
- Create a new file called vpc.tf in your project directory and add the availability zone data source and the VPC module's core networking configuration:
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
What does this code do?
- data "aws_availability_zones" fetches the list of availability zones in your region, filtering out opt-in zones like Local Zones so you only get standard ones.
- source and version tell Terraform to download version 6.6.1 of the VPC module from the Terraform registry.
- cidr = "10.0.0.0/16" defines the IP address range for the entire VPC, giving you 65,536 addresses to work with.
- private_subnets are where your EKS worker nodes will run. public_subnets are for resources that need direct internet access, like load balancers.
- single_nat_gateway = true uses one NAT gateway instead of one per availability zone. This saves cost for a learning project.
- Still inside the module "vpc" block, add the EKS subnet tags and general tags below enable_dns_hostnames = true, then close the block with a }:
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
}
tags = {
Terraform = "true"
Environment = "dev"
}
}
What are these subnet tags for?
kubernetes.io/role/elb = 1 on public subnets tells Kubernetes where to place internet-facing load balancers. kubernetes.io/role/internal-elb = 1 on private subnets tells Kubernetes where to place internal load balancers.
Without these tags, EKS cannot automatically discover which subnets to use when you create a Kubernetes Service of type LoadBalancer later in the project.
- Save vpc.tf.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before continuing.
ⓧ I'd like to double check the full code
Your vpc.tf file should contain both chunks you built above, in order:
- The data "aws_availability_zones" block at the top, filtering for standard zones.
- The module "vpc" block with source, version, name, CIDR, subnets, NAT gateway settings, subnet tags, and general tags.
Scroll up and confirm each section is present. Make sure the module block's closing } is at the very end of the file.
You added a new module to your configuration, so Terraform needs to download it before you can apply.
- Download the VPC module by running this command:
terraform init
You should see Terraform downloading the VPC module, followed by the message Terraform has been successfully initialized!
Seeing an error instead of a successful init?
- Check that your vpc.tf has a closing } for both the data block and the module block. A missing brace is the most common cause of init failures here.
- Confirm the source is exactly terraform-aws-modules/vpc/aws with no typos or extra slashes.
- Terraform init failing for me. What should I check?
Create the outputs and apply
Terraform outputs let you extract key values from your infrastructure so you can reference them later. You need the VPC ID so the EKS module can find the VPC in the next step.
- Create a new file called outputs.tf and add the VPC ID output:
output "vpc_id" {
description = "The ID of the VPC"
value = module.vpc.vpc_id
}
What does this output do?
module.vpc.vpc_id pulls the VPC ID from the VPC module's outputs. After terraform apply finishes, you can run terraform output vpc_id to see the actual VPC ID that AWS assigned.
- Save outputs.tf.
Before you run this, how many resources do you think Terraform will create for a VPC with 4 subnets, route tables, a NAT gateway, and an internet gateway?
- Provision your VPC on AWS by running this command:
terraform apply
What happens when you run terraform apply?
Terraform first shows you an execution plan listing every resource it will create. It then prompts you with Do you want to perform these actions? and waits for your confirmation.
- Type yes and press Enter to confirm.
The apply takes about 2-3 minutes. You will see resources being created one by one as Terraform provisions your VPC infrastructure.
When the apply finishes, you should see a summary line like Apply complete! Resources: X added, 0 changed, 0 destroyed. The VPC module typically creates around 20 resources.
- Confirm your VPC was created by running this command:
terraform output vpc_id
You should see a VPC ID printed, something like vpc-0abc123def456789. Your exact ID will be different. This confirms the VPC is live on AWS.
Terraform apply failed?
- If you see an authentication error, confirm your AWS CLI credentials are configured correctly. Run aws sts get-caller-identity to check.
- If you see a syntax error in your HCL, double check the tag blocks in vpc.tf. Each tag key needs to be quoted (e.g. "kubernetes.io/role/elb") because it contains special characters.
- If you see a module-not-installed error, run terraform init first. Terraform needs to download the VPC module before it can apply.
- My terraform apply is failing. Help me debug.
Your VPC is live on AWS with properly tagged subnets, a NAT gateway, and an internet gateway. Next up, you will use this VPC to provision the EKS cluster itself.
Provision the EKS Cluster
Your VPC is live with subnets, a NAT gateway, and the right tags for EKS. Now you need the actual cluster that runs Kubernetes inside that network.
The terraform-aws-modules/eks/aws module handles the control plane, IAM roles, security groups, and managed node groups in a single module block. In this step, you will provision the cluster and connect kubectl to it.
In this step, get ready to:
- Create the EKS module configuration in eks.tf.
- Apply the infrastructure and wait for the cluster to provision.
- Connect kubectl to the cluster and verify the nodes are ready.
Create the EKS module configuration
An EKS cluster requires a control plane, IAM roles, security groups, and a managed node group. The EKS community module bundles all of these into a single block of Terraform configuration.
- Create a new file called eks.tf in your project directory and paste the following code:
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "21.24.0"
cluster_name = var.cluster_name
cluster_version = "1.36"
cluster_endpoint_public_access = true
enable_cluster_creator_admin_permissions = true
# Connect to the VPC from the previous step
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
# Worker node configuration
eks_managed_node_groups = {
default = {
instance_types = ["t3.small"]
min_size = 2
max_size = 3
desired_size = 2
}
}
tags = {
Terraform = "true"
Environment = "dev"
}
}
What does this code do?
- source and version pin the official EKS community module at version 21.24.0.
- cluster_version = "1.36" sets the Kubernetes version to the latest in EKS standard support.
- cluster_endpoint_public_access = true lets you reach the cluster's API server from your local machine with kubectl.
- enable_cluster_creator_admin_permissions = true grants your current AWS identity administrator access to the cluster via an access entry.
- vpc_id and subnet_ids wire the cluster into the VPC you created in the previous step. The worker nodes land in the private subnets.
- eks_managed_node_groups defines a group called default with 2 t3.small EC2 instances. The auto-scaling range allows up to 3 nodes.
Why t3.small instead of t3.micro?
EKS system pods (CoreDNS, kube-proxy, VPC CNI) consume around 0.5 GB of memory on each node. A t3.micro only has 1 GB total, leaving almost nothing for your workloads. t3.small (2 GB) is the practical minimum for EKS.
- Save eks.tf.
- Download the new EKS module by running this command:
terraform init
You should see output mentioning the EKS module being installed, ending with "Terraform has been successfully initialized".
Seeing a module error?
- Check that source is exactly "terraform-aws-modules/eks/aws" with no typos.
- Make sure version is set to "21.24.0" (in quotes).
- Confirm your internet connection is active. Terraform needs to download the module from the registry.
Help me troubleshoot a terraform init failure for the EKS module.
✔️ Awesome, I've got everything!
Great. Double check you have saved eks.tf.
ⓧ I'd like to double check the full code
Here is the complete eks.tf file. Make sure yours matches exactly:
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "21.24.0"
cluster_name = var.cluster_name
cluster_version = "1.36"
cluster_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 = {
instance_types = ["t3.small"]
min_size = 2
max_size = 3
desired_size = 2
}
}
tags = {
Terraform = "true"
Environment = "dev"
}
}
Add cluster outputs and apply the infrastructure
Terraform outputs let you extract key values from your infrastructure after it is provisioned. You need the cluster endpoint and name for configuring kubectl in the next substep.
- Open outputs.tf and add the following code below the existing vpc_id output:
# EKS cluster outputs
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
}
What do these outputs do?
- cluster_endpoint is the HTTPS URL that kubectl uses to communicate with the EKS control plane.
- cluster_name is the name you will pass to the AWS CLI when configuring kubectl.
- Save outputs.tf.
- Apply the full infrastructure by running this command:
terraform apply
What happens when you run this?
Terraform shows you a plan listing all the resources it will create. Type yes and press Enter to confirm.
EKS cluster creation takes approximately 10-15 minutes. This is a known characteristic of the service. Leave the terminal running and grab a coffee while it provisions the control plane, node group, and all supporting resources.
When it finishes, you should see a line reading "Apply complete!" followed by the count of resources added.
Apply failed or timed out?
- If you see an access denied error, confirm your AWS CLI credentials have permissions for EKS, EC2, and IAM.
- If the apply hangs for more than 20 minutes, check the EKS console in your browser to see whether the cluster is still in a "Creating" state.
- If you see a subnet or VPC error, make sure vpc.tf was applied successfully in the previous step by running terraform output vpc_id.
Help me debug a terraform apply failure for EKS.
✔️ Awesome, I've got everything!
Great. Double check you have saved outputs.tf.
ⓧ I'd like to double check the full code
Here is the complete outputs.tf file including the VPC output from the previous step:
output "vpc_id" {
description = "The ID of the VPC"
value = module.vpc.vpc_id
}
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
}
Connect kubectl to the cluster
Your EKS cluster is running, but kubectl on your local machine does not know about it yet. You need to update your kubeconfig file so kubectl can authenticate and send commands to the right cluster.
- Point kubectl at your new cluster by running this command:
aws eks update-kubeconfig --region us-east-1 --name nextwork-eks
What does this command do?
The AWS CLI fetches connection details (endpoint URL, certificate, auth token) from your EKS cluster and writes them into your local ~/.kube/config file. After this, every kubectl command automatically targets your nextwork-eks cluster.
You should see a confirmation message that a new context was added to your kubeconfig.
Before you run the next command, think about what you expect to see. You asked Terraform for 2 nodes with a desired_size of 2. How many nodes should appear, and what status should they have?
- Verify the worker nodes are running by running this command:
kubectl get nodes
You should see 2 nodes listed, both with a STATUS of Ready. That confirms your EKS cluster is fully operational with both worker nodes accepting workloads.
Nodes not showing or not Ready?
- If you see "No resources found", wait 2-3 minutes and try again. The managed node group may still be launching instances.
- If nodes appear but are not in Ready status, give them another minute. Nodes go through a brief initialization phase after joining the cluster.
- If you see a connection error, confirm the aws eks update-kubeconfig command completed successfully in the previous step.
Help me troubleshoot why my EKS nodes are not showing as Ready.
Your EKS cluster is live with 2 worker nodes ready to run containers. Next up, you will deploy an Nginx application and expose it to the internet to prove the cluster can actually serve traffic.
Deploy an Application
Your EKS cluster is running with 2 healthy nodes. That is the hard part done.
A cluster with no workload is just an expensive idle machine. In this step, you will deploy an Nginx application and expose it to the internet through a Kubernetes LoadBalancer service to prove the cluster actually works.
In this step, get ready to:
- Create a Kubernetes Deployment and Service manifest.
- Deploy Nginx and access its welcome page via a LoadBalancer URL in your browser.
Create the deployment manifest
Kubernetes workloads are defined in YAML manifest files. You will create a single file that contains two resources: a Deployment that tells Kubernetes how many copies of your app to run, and a Service that exposes those copies to the internet.
- Create a k8s directory inside your Terraform project folder by running this command:
mkdir k8s
- Create a new file called k8s/deployment.yaml in your text editor and paste the following Deployment definition:
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:latest
ports:
- containerPort: 80
What does this Deployment do?
- replicas: 2 tells Kubernetes to run 2 identical copies (pods) of your app for reliability.
- image: nginx:latest pulls the official Nginx container image from Docker Hub.
- containerPort: 80 exposes port 80 inside each pod, which is where Nginx listens by default.
- The selector and labels with app: nginx link the Deployment to its pods so Kubernetes knows which pods belong to this Deployment.
- Confirm the file was created by running:
ls k8s/
You should see deployment.yaml listed in the output.
- Now add the Service definition below the Deployment in k8s/deployment.yaml. Paste the following at the very bottom of the file:
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
What does the Service do?
- The --- separator tells Kubernetes this is a second resource in the same file.
- type: LoadBalancer tells AWS to create a real load balancer with a public DNS name that anyone on the internet can reach.
- selector: app: nginx connects this Service to the pods created by the Deployment above, matching on the same label.
- port: 80 is what the load balancer listens on. targetPort: 80 is where traffic gets forwarded to inside the pods.
- Save k8s/deployment.yaml.
✔️ Awesome, I've got everything!
Great. Double check you have saved your file before continuing.
ⓧ I'd like to double check the full code
Your k8s/deployment.yaml should contain two resource definitions, in order:
- The Deployment block (starting with apiVersion: apps/v1). This defines nginx-deployment with 2 replicas of nginx:latest on port 80.
- A --- separator line between the two resources.
- The Service block (starting with apiVersion: v1). This defines nginx-service of type LoadBalancer on port 80.
Scroll up and confirm each block is present and matches the code above.
Apply the manifest and verify
- Deploy both resources to your cluster by running this command:
kubectl apply -f k8s/deployment.yaml
You should see confirmation that both the deployment and the service were created.
What does kubectl apply do?
kubectl apply reads your YAML file and sends the resource definitions to the Kubernetes API server running on your EKS cluster. The API server then schedules the pods onto your worker nodes and provisions the AWS load balancer.
Seeing a connection error?
- Make sure your kubeconfig is still pointing at the nextwork-eks cluster. Re-run aws eks update-kubeconfig --region us-east-1 --name nextwork-eks to reconfigure it.
- Check that the file path is correct. You should be running the command from your Terraform project folder (the parent of k8s/).
- Still stuck? Help me debug why kubectl apply is failing against my EKS cluster.
- Confirm both Nginx pods are running by running:
kubectl get pods
You should see 2 pods with names starting with nginx-deployment, both showing a STATUS of Running.
Pods not showing Running status?
- If pods show Pending or ContainerCreating, wait 30 seconds and run kubectl get pods again. The nodes need time to pull the Nginx image.
- If pods show ImagePullBackOff, the nodes cannot reach Docker Hub. Confirm your VPC has the NAT gateway set up so private subnets can access the internet.
- Still stuck? Help me debug why my Nginx pods are not running on EKS.
Now for the real test. Before you run the next command, think about this: do you expect the EXTERNAL-IP column to have a URL right away, or will it show <pending> first?
- Check the LoadBalancer URL by running:
kubectl get svc nginx-service
The EXTERNAL-IP column shows the DNS name of the AWS load balancer that was created for your service. It may take 1-2 minutes to provision. If you see <pending>, wait a minute and run the command again.
Why does the load balancer take time?
When you create a Service of type LoadBalancer on EKS, AWS provisions a real load balancer behind the scenes. This involves creating the resource, registering your nodes as targets, and assigning a DNS name. That process takes a minute or two to complete.
- Once you see a URL in the EXTERNAL-IP column, copy it and paste it into your browser's address bar.
You should see the "Welcome to nginx!" page. That page is being served by your Nginx pods, running on your EKS worker nodes, reachable from the public internet through the AWS load balancer.
Page not loading in the browser?
- Make sure you are using http:// (not https://) in your browser. The service is not configured with TLS.
- The load balancer may need another minute to finish registering your nodes. Wait 1-2 minutes and try refreshing.
- Confirm the public subnets in your VPC are tagged with kubernetes.io/role/elb = 1. Without this tag, EKS does not know which subnets to place the load balancer in.
- Still stuck? Help me debug why my Nginx LoadBalancer URL is not loading in the browser.
Your cluster is serving live traffic from the internet. Next up, you will tear down every single resource you created with one command, proving that Infrastructure as Code makes cleanup just as easy as provisioning.
Tear Down Everything
In Step 4 you deployed an Nginx app and accessed it through a live URL. That proves your EKS cluster works. It also means your cluster, node group, VPC, NAT gateway, and load balancer are all running and costing money by the hour.
In this step, you will tear everything down. The goal is zero orphaned resources and zero surprise charges. You will delete the Kubernetes service first to release the AWS load balancer, then run a single terraform destroy command to clean up everything else.
In this step, get ready to:
- Delete the Kubernetes deployment and service to release the AWS load balancer.
- Run terraform destroy to remove all remaining infrastructure.
- Verify zero billable resources remain in your AWS account.
Delete the Kubernetes resources first
The LoadBalancer service you created in Step 4 told AWS to provision a Classic Load Balancer. This load balancer lives outside of Terraform's state. If you skip this step and jump straight to terraform destroy, Terraform cannot clean up that load balancer because it does not know it exists.
- Delete the Kubernetes deployment and service by running:
kubectl delete -f k8s/deployment.yaml
Why delete Kubernetes resources before Terraform?
LoadBalancer services in Kubernetes create AWS Elastic Load Balancers automatically. These load balancers are not tracked in Terraform's state file because Kubernetes created them, not Terraform.
If you destroy the VPC while the load balancer still exists, Terraform will fail to delete subnets and security groups that the load balancer depends on. The orphaned load balancer would also continue incurring charges.
- Confirm the service is gone by running:
kubectl get svc
You should see only the default kubernetes ClusterIP service listed. The nginx-service row is gone.
Still seeing nginx-service?
Make sure you ran the command from inside your project directory where k8s/deployment.yaml is located. If the file path is wrong, kubectl cannot find the resources to delete.
help me debug why kubectl delete is not removing my nginx-service
Destroy all infrastructure with Terraform
With the load balancer released, Terraform can now safely destroy every remaining AWS resource. The EKS cluster, node group, VPC, subnets, NAT gateway, internet gateway, IAM roles, and security groups will all be removed in reverse dependency order.
- Begin the teardown by running:
terraform destroy
- Terraform displays a destruction plan listing every resource it will remove. Type yes when prompted to confirm.
What happens during terraform destroy?
Terraform reads its state file to build a dependency graph of all resources. It then deletes them in reverse order, starting with resources that depend on others (like the EKS node group) before deleting the resources they depend on (like the VPC subnets).
This is the mirror image of terraform apply. Apply creates from the bottom up. Destroy removes from the top down.
This takes about 10 minutes
Destroying the EKS cluster takes roughly 10 minutes, similar to when you created it. You will see Terraform printing status updates as each resource is removed.
Once terraform destroy finishes, it prints a summary showing how many resources were destroyed.
Before you run the next command, do you think terraform destroy caught everything? Or might something still be lingering in your account?
- Verify no EKS clusters remain by running:
aws eks list-clusters --region us-east-1
The output should show an empty list of clusters. Because you deleted the Kubernetes service first and then ran terraform destroy, there is nothing left.
- For extra confidence, open the AWS console in your browser.
- Search for EKS in the search bar and open the Elastic Kubernetes Service page.
- Confirm that no clusters are listed in us-east-1.
Destroy failed with dependency errors?
If Terraform reports errors about resources that could not be deleted due to dependencies, a load balancer may still be attached to the VPC. Run kubectl get svc --all-namespaces to check for remaining LoadBalancer services, delete them, then run terraform destroy again.
help me fix terraform destroy dependency errors on my EKS project
Secret mission
Add a Spot Instance Node Group
In production, running every workload on on-demand EC2 instances gets expensive. Can you add a second node group that uses spot instances to cut compute costs?
Clean Up Your Resources
Clean Up Your Resources
You already ran terraform destroy during the project, so all AWS resources have been deleted and no costs are accruing. Decide whether to keep your local project files for reference or delete them entirely.
Cost warning
If terraform destroy was interrupted or failed, your AWS resources continue accruing charges. The EKS control plane alone costs $0.10 per hour.
Open the AWS console and confirm no EKS clusters, EC2 instances, or VPCs remain in us-east-1. If any resources remain, run terraform destroy again from your project folder.
Resources you used:
- AWS EKS cluster, VPC, EC2 instances, NAT gateway, and Load Balancer (already destroyed via terraform destroy).
- Local Terraform configuration files (versions.tf, providers.tf, variables.tf, vpc.tf, eks.tf, outputs.tf).
- Kubernetes manifest (k8s/deployment.yaml).
- Terraform cache and state files (.terraform/ directory and terraform.tfstate).
- Stale kubeconfig context for the destroyed nextwork-eks cluster.
Keep everything running
No action needed. Choose this if you plan to re-provision the cluster or reference your Terraform code later.
- Your AWS resources are already destroyed, so no cloud costs are accruing.
- Your local project files remain on your Desktop for reference.
- Your kubeconfig still has a stale context for nextwork-eks. This is harmless but will fail if you try to use it.
- To re-provision the full cluster later, run terraform apply from your project folder.
Pause - I'll come back to this later
EKS does not support pausing. The control plane runs 24/7 once provisioned, so there is no way to suspend costs without destroying the cluster.
- Since you already ran terraform destroy, there is nothing to pause. Your AWS resources are fully deleted.
- Your local Terraform files are still on your Desktop. You can re-provision the entire cluster at any time by running terraform apply from your project folder.
Delete - I don't want to use this again
Remove all local project files and clean up stale configuration. Your AWS resources are already destroyed.
- Verify your AWS resources are gone by opening the EKS page in the AWS console. Confirm no clusters appear in the us-east-1 region.
- Delete your local project folder from your Desktop. Open Finder (macOS) or File Explorer (Windows), navigate to your Desktop, and delete the folder containing your .tf files and the k8s/ directory.
- Remove the stale kubeconfig context by running:
kubectl config delete-context [[KUBE_CONTEXT="your nextwork-eks context name from kubectl config get-contexts"]]
Find your context name
Run kubectl config get-contexts to list all contexts. Look for the one containing nextwork-eks in its name. Copy that full name and paste it into the command above.
Nice Work!
Nice Work!
Nice work! You just built a production-style EKS cluster on AWS, provisioned entirely through Terraform code, deployed a live application to it, and tore everything down without leaving a single orphaned resource behind.
You've learned how to:
- Write a complete Terraform project from scratch using community modules to provision a VPC with public and private subnets and an EKS cluster with a managed node group.
- Deploy a live Nginx application to Kubernetes and expose it to the internet through a LoadBalancer service.
- Tear down all infrastructure cleanly with terraform destroy, leaving zero billable resources behind on your AWS account.
- Completed an optional Secret Mission to push your infrastructure skills even further.
Ready to quiz yourself?