Deploy a Kubernetes Cluster on AWS EKS with Terraform — Complete 2026 Guide

Muhammad Hassan Javed
August 9, 2026
~18 min read
Kubernetes Terraform AWS DevOps
Direct Answer

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

INTERNET │ ▼ [ Internet Gateway ] │ ┌──▼────────────────────────────────────────────────────────┐ │ VPC (10.0.0.0/16) │ │ │ │ ┌────────────────────────┐ ┌────────────────────────┐ │ │ │ Public Subnets (3x) │ │ Private Subnets (3x) │ │ │ │ (ALB / NAT Gateways) │──►│ (EKS Node Groups) │ │ │ └────────────────────────┘ └───────────┬────────────┘ │ │ │ │ └───────────────────────────────────────────┼───────────────┘ ▼ ┌────────────────────────┐ │ AWS EKS Control Plane │ │ (API Server, etcd) │ └────────────────────────┘

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
  • kubectl installed (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:

Directory Structure
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.

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.

vpc.tf
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.

eks.tf
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.

variables.tf
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:

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 "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:

providers.tf
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]
    }
  }
}
terraform.tfvars
# 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.

Terminal
# 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.

Terminal
# 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

How much does an EKS cluster cost?

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.

eksctl vs Terraform for EKS?

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.

How to migrate from aws-auth to Access Entries?

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.

Why does terraform destroy hang on EKS?

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 vs IRSA?

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!

Muhammad Hassan Javed

DevOps & Cloud Infrastructure Engineer