Deploy a Kubernetes Cluster on AWS EKS with Terraform — Complete 2026 Guide
To deploy an EKS cluster with Terraform, initialize a project using the terraform-aws-modules/eks and terraform-aws-modules/vpc modules. Configure your provider, define a VPC with private subnets for security, and declare the EKS cluster resource specifying API authentication mode and managed node groups. Finally, run terraform init followed by terraform apply.
Introduction
Managing Kubernetes clusters manually through the AWS Console is prone to human error and difficult to replicate. Infrastructure as Code (IaC) is the industry standard, and Terraform, paired with AWS EKS (Elastic Kubernetes Service), provides a declarative, version-controlled approach to cloud-native infrastructure.
In this guide, we will build a production-ready EKS architecture from scratch using the latest Terraform modules (v21+). We will leverage the modern EKS Access Entries API (goodbye aws-auth ConfigMap!) and implement security best practices natively.
What We'll Build: Architecture Overview
Prerequisites Checklist
Before diving into the code, ensure your local environment is prepared:
- AWS Account with Administrator IAM Access
- AWS CLI v2 installed and configured (
aws configure) - Terraform >= 1.5.0 installed
-
kubectlinstalled (matching EKS version) - Basic understanding of Kubernetes core concepts
Step 1: Project Structure & Remote Backend
A maintainable Terraform project separates concerns. Create a new directory and structure your files like this:
eks-terraform/
├── versions.tf
├── providers.tf
├── variables.tf
├── vpc.tf
├── eks.tf
├── outputs.tf
└── terraform.tfvars
First, configure the required providers and the S3 backend for state storage in versions.tf.
terraform {
required_version = ">= 1.5.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.50"
}
helm = {
source = "hashicorp/helm"
version = "~> 2.13"
}
}
backend "s3" {
bucket = "techwithhassan-terraform-state" # Replace with your bucket
key = "eks-cluster/terraform.tfstate"
region = "us-east-1"
dynamodb_table = "techwithhassan-terraform-locks" # For state locking
encrypt = true
}
}
Step 2: VPC Network Architecture
EKS requires a robust network layer. We will use the official AWS VPC module to provision public subnets (for NAT Gateways and external Load Balancers) and private subnets (where our worker nodes and pods will run).
Note the crucial tags added to the subnets—these allow the AWS Load Balancer Controller to automatically discover where to deploy resources.
data "aws_availability_zones" "available" {
state = "available"
}
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.8.1"
name = "${var.cluster_name}-vpc"
cidr = var.vpc_cidr
azs = slice(data.aws_availability_zones.available.names, 0, 3)
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
# Crucial tags for Kubernetes load balancers
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
}
}
Step 3: EKS Cluster & Managed Node Groups
Now for the main event. We use the terraform-aws-modules/eks/aws module v21. We configure authentication_mode = "API" to use Access Entries instead of the deprecated aws-auth configmap.
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "21.24.1"
cluster_name = var.cluster_name
cluster_version = var.cluster_version
vpc_id = module.vpc.vpc_id
subnet_ids = module.vpc.private_subnets
cluster_endpoint_public_access = true
# EKS Addons
cluster_addons = {
coredns = {}
kube-proxy = {}
vpc-cni = {}
eks-pod-identity-agent = {}
}
# Use modern Access Entries for Auth
enable_cluster_creator_admin_permissions = true
authentication_mode = "API"
# Default Managed Node Group
eks_managed_node_groups = {
default_node_group = {
instance_types = ["t3.medium"]
min_size = 2
max_size = 5
desired_size = 2
}
}
}
Step 4: Variables & Outputs
To make the module reusable, define the inputs and outputs.
variable "aws_region" {
description = "AWS Region"
type = string
default = "us-east-1"
}
variable "cluster_name" {
description = "Name of the EKS cluster"
type = string
}
variable "cluster_version" {
description = "Kubernetes version to use"
type = string
default = "1.30"
}
variable "vpc_cidr" {
description = "VPC CIDR range"
type = string
default = "10.0.0.0/16"
}
Next, define the Terraform outputs so you can easily retrieve your cluster details after deployment:
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 "cluster_security_group_id" {
description = "Security group ID for the cluster"
value = module.eks.cluster_security_group_id
}
output "cluster_certificate_authority_data" {
description = "Base64 encoded certificate data for cluster auth"
value = module.eks.cluster_certificate_authority_data
}
output "region" {
description = "AWS region"
value = var.aws_region
}
Finally, create your providers.tf to configure the AWS provider and a terraform.tfvars with your specific values:
provider "aws" {
region = var.aws_region
}
provider "helm" {
kubernetes {
host = module.eks.cluster_endpoint
cluster_ca_certificate = base64decode(module.eks.cluster_certificate_authority_data)
exec {
api_version = "client.authentication.k8s.io/v1beta1"
command = "aws"
args = ["eks", "get-token", "--cluster-name", module.eks.cluster_name]
}
}
}
# Customize these values for your environment
aws_region = "us-east-1"
cluster_name = "my-eks-cluster"
cluster_version = "1.30"
vpc_cidr = "10.0.0.0/16"
Step 5: Deploy the Cluster
With all code in place, initialize your workspace and apply the configuration. This process will take approximately 15-20 minutes as the EKS control plane and node groups are provisioned.
# Initialize terraform providers and state
$ terraform init
# Review the execution plan
$ terraform plan
# Execute the deployment
$ terraform apply -auto-approve
# After completion, configure your local kubectl
$ aws eks update-kubeconfig --region us-east-1 --name my-eks-cluster
# Verify the cluster is ready
$ kubectl get nodes
NAME STATUS ROLES AGE VERSION
ip-10-0-1-23.ec2.internal Ready <none> 5m v1.30.0-eks
ip-10-0-2-45.ec2.internal Ready <none> 5m v1.30.0-eks
Step 6: Day-2 Operations
Once deployed, your EKS cluster needs controllers to integrate fully with AWS. The most critical is the AWS Load Balancer Controller, which automatically provisions Application Load Balancers (ALBs) or Network Load Balancers (NLBs) when you create an Ingress or a Service of type LoadBalancer.
Use Helm to deploy it into the kube-system namespace, ensuring it is attached to the appropriate IAM role via IRSA or Pod Identity.
Step 7: Production Hardening
The configuration above is a great starting point, but for a true production environment, consider these hardened settings:
| Feature | Development Setting | Production Recommendation |
|---|---|---|
| API Server Access | Public Endpoint (true) |
Private Only (true) + VPN/Direct Connect |
| Control Plane Logging | Disabled | Enabled (API, Audit, Authenticator) |
| Secret Encryption | AWS Default | KMS Envelope Encryption enabled |
| Node Scaling | Managed Node Groups | Karpenter for faster, cost-optimized scaling |
Step 8: Clean Teardown
When you are done testing, you must tear down the infrastructure to avoid unexpected AWS charges. Crucial step: delete all Kubernetes services of type LoadBalancer and Ingresses first, otherwise Terraform will hang trying to delete the VPC because AWS creates ENIs that Terraform is unaware of.
# 1. Delete K8s resources that spawn AWS infrastructure
$ kubectl delete svc --all
$ kubectl delete ingress --all
# 2. Destroy Terraform infrastructure
$ terraform destroy -auto-approve
Frequently Asked Questions
The EKS control plane costs $0.10 per hour (about $73/month). Additionally, you pay for the EC2 instances in your node groups, EBS volumes, Load Balancers, and NAT Gateways. A minimal production cluster typically starts around $150-$200/month.
While eksctl is great for quick testing and local development, Terraform is the industry standard for production environments. Terraform allows you to manage EKS alongside your VPC, databases, and IAM roles in a single state file with code review processes.
In Terraform EKS module v20+, set authentication_mode = 'API'. You can use the aws_eks_access_entry resource or the module's access_entries variable to define access, completely bypassing the legacy aws-auth ConfigMap.
This usually happens because Kubernetes creates AWS resources (like LoadBalancers or EBS volumes) outside of Terraform's knowledge. You must delete all K8s services of type LoadBalancer, Ingresses, and PVCs before running terraform destroy.
EKS Pod Identity is the newer, simpler way to grant AWS permissions to Pods. It doesn't require OIDC providers or complex trust policies like IRSA (IAM Roles for Service Accounts). Just install the EKS Pod Identity Agent addon and map roles to service accounts.
Conclusion
You've successfully architected and deployed a production-grade AWS EKS cluster using Terraform. By using the official AWS modules, we abstracted away hundreds of lines of complex HCL while still maintaining total control over our VPC layout, authentication mechanisms, and node group specifications.
To view the complete source code for this tutorial, check out the GitHub Repository.
If you found this guide helpful, make sure to subscribe to the Tech With Hassan YouTube channel for more DevOps and Cloud Infrastructure deep dives!