April 10, 2026
Comprehensive Guide to Provisioning an AWS EKS Cluster with Terraform
In my previous post, we walked through deploying a highly available three-tier architecture using Terraform. The approach is classic for…

By M Bilal
7 min read
In my previous post, we walked through deploying a highly available three-tier architecture using Terraform. The approach is classic for reason — it's robust and reliable. However, the way we build and scale software is shifting. In 2026, the focus has moved from managing individual servers to managing outcomes. Today, we're taking a significant leap forward by embracing containerization and orchestration with cloud-native platform Kubernetes.
Why are we moving to Containers?
If you've spent years managing Virtual Machines, you know the pain of "it works on my machine" and the overhead of booting entire operating systems for tiny services. Containerization fixes this: Containerization offers several advantages over traditional VM-based deployments:
- Efficiency that impacts the bottom line: Containers share the host OS kernel, meaning they boot in seconfs and use fraction of RAM?CPU compared to VMs.
- Environment Parity: A container is an immutable artifact. Whether it's on a developer's laptop or a production EKS cluster, the environment remains identical.
- Rapid Scaling: When traffic spikes, you can spin up dozens of containers in the time it takes to boot a single EC2 instance.
- Modern DevOps Workflows: Containers are the native language of CI/CD. They make microservices manageable and allow for granular isolation without the "VM tax."
The Kubernetes Advantage in 2026
Kubernetes has evolved beyond just "Orchestration". It is now the operating system of the cloud. By using it, you gain:
- Self-Healing Infrastructure: If a container crashes, Kubernetes replaces it instantly. If a node fails, it moves your workloads to healthy hardware.
- Cloud Portability: It protects your business from vendor lock-in. If you build for Kubernetes, moving from AWS to Azure or Google Cloud becomes a migration of configuration, not a rewrite of your architecture.
- AI & Data Scale: Modern AI workloads — from model training to real-time inference — rely on Kubernetes to manage expensive GPU resources and massive data pipelines efficiently.
Amazon EKS & Managed Node Groups: Best of Both Worlds
Setting up Kubernetes from scratch is a full-time job. Amazon EKS (Elastic Kubernetes Service) removes that burden by providing a fully managed, high-availability control plane. AWS handles the API server and etcd (the "brain" of your cluster), so you don't have to.
To make management even easier, we'll be using Managed Node Groups. In a production environment, you don't want to be manually patching OS kernels on worker nodes. Managed Node Groups automate the heavy lifting:
- Lifecycle Automation: AWS automatically provisions and registers the EC2 instances for your cluster.
- Zero-Downtime Updates: When it's time to update your Kubernetes version, AWS handles the "rolling update" of your nodes, draining pods gracefully to ensure your users never notice a thing.
- Integrated Security: Managed nodes come with EKS-optimized AMIs that are pre-hardened and integrated with AWS IAM for secure, identity-based access.
By the end of this guide, you won't just have a cluster running; you'll have a scalable, secure, and production-ready platform that follows the same standards I use when building infrastructure for my clients.
Guide Overview: Your Roadmap to EKS Mastery
In this comprehensive guide, we aren't just clicking buttons in the console. I'll walk you through a modular, scalable approach to provisioning an AWS EKS cluster using Terraform. This workflow is designed to be "Production-Ready", meaning it includes the security, observability, and automation required by the modern enterprises.
This tutorial is broken down into six logical phases:
- Networking (VPC): Building a secure, multi-AZ foundation with proper tagging for EKS and Karpenter.
- The Control Plane: Provisioning the EKS cluster and Managed Node Groups.
- Cluster-Addons: Using Helm and Terraform to install essential controllers (AWS Load Balancer, VPC-CNI, etc.).
- Identity & Access (IAM): Mastering fine-grained access control for users and internal roles.
- Smart Scaling: Implementing Karpenter for lightning-fast compute scaling and HPA for workload demand.
- Secret Management: Securing your credentials using AWS Secrets Manager and the CSI Driver.
Pro Tip: To maintain a "Single Source of Truth," we will use HCP Terraform (formerly Terraform Cloud). This ensures our state files are versioned, locked, and accessible to team members without the manual overhead of managing S3 buckets and DynamoDB tables.
Part 1: AWS VPC Creation using Terraform (The Networking Foundation)
Before we deploy a single container, we need a place for them to live. In the world of AWS, that is your VPC (Virtual Private Cloud).
Think of the VPC as the "security perimeter" of your data center in the cloud. A poorly designed network is the leading cause of EKS deployment failures, usually due to IP exhaustion or incorrect routing. In this section, we will build a network that follows the AWS Well-Architected Framework, ensuring your cluster is highly available across multiple Availability Zones (AZs).
Why this design matters for EKS:
- Security: We isolate worker nodes in Private Subnets so they aren't reachable directly from the internet.
- High Availability: By spreading our subnets across multiple AZs, our cluster can survive a localized AWS outage.
- Scalability: We will use a
/16CIDR block to ensure we never run out of IP addresses as our pod count grows.
Architecture Diagram:
Core VPC Components:
To build this, our Terraform configuration will provision:
- The VPC: Our isolated virtual network.
- Public Subnets: For the Internet Gateway and Load Balancers.
- Private Subnets: Where our EKS worker nodes (EC2s) will live, safely tucked away from direct internet access.
- NAT Gateway: Allowing our private nodes to download updates or reach external APIs securely.
- Flow Logs: An audit trail for every packet entering or leaving the network.
Network Architecture:
Our eks-vpc.tf file utilizes the industry-standard AWS VPC module. Here's the breakdown of the logic we're implementing:
- CIDR Strategy: We're using
100.65.0.0/16. This provides a massive IP space, which is critical because, by default, every Kubernetes Pod in EKS gets its own unique IP from your VPC. - Subnet Math: * Private Subnets: We use a
/20mask per AZ. This gives us roughly 4,000 IPs per subnet—plenty of room for scaling large clusters. - Public Subnets: We use a smaller
/24mask since these only house Load Balancers and NAT Gateways. - Smart Tagging: This is where many engineers trip up. We are explicitly tagging subnets so that the AWS Load Balancer Controller knows exactly where to deploy Public vs. Private Load Balancers. We've also added the
karpenter.sh/discoverytag, which is the "secret sauce" that allows our auto-scaler to find the right subnets later in the series. - Cost vs. Availability: For this guide, I've enabled a Single NAT Gateway.
- Senior Architect's Note: In a high-stakes production environment, you should use one NAT Gateway per Availability Zone. However, for development or smaller production loads, a single NAT Gateway significantly reduces your monthly AWS bill while still providing full functionality.
This architecture provides a solid foundation for running an EKS cluster with public and private subnets, NAT gateway for outbound internet access from private subnets, and appropriate tagging for Kubernetes and Karpenter integration. The VPC flow logs will help with network monitoring and troubleshooting.
The Code: Defining the VPC
Here is the Terraform implementation. Notice we are using a specific version of the module to ensure our infrastructure remains stable and predictable.
################################################################################
# AWS VPC configuration
################################################################################
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.8.0" # Using the latest 2026 stable release
name = "${local.name}-vpc"
cidr = local.vpc_cidr_blocks
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr_blocks, 4, k)]
public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr_blocks, 8, k + 48)]
public_subnet_suffix = "SubnetPublic"
private_subnet_suffix = "SubnetPrivate"
# Infrastructure Cost Management
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
enable_dns_support = true
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
"Tier" = "Public"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
"Tier" = "Private"
# Essential for Karpenter v1.0+ node provisioning
"karpenter.sh/discovery" = local.name
}
# Observability & Compliance
enable_flow_log = true
flow_log_destination_type = "cloud-watch-logs"
create_flow_log_cloudwatch_log_group = true
create_flow_log_cloudwatch_iam_role = true
flow_log_cloudwatch_log_group_retention_in_days = 7
# Detailed log format for deep network troubleshooting
flow_log_log_format = "$${interface-id} $${srcaddr} $${srcport} $${pkt-src-aws-service} $${dstaddr} $${dstport} $${pkt-dst-aws-service} $${protocol} $${flow-direction} $${traffic-path} $${action} $${log-status} $${subnet-id} $${az-id}"
tags = local.tags
}################################################################################
# AWS VPC configuration
################################################################################
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "~> 5.8.0" # Using the latest 2026 stable release
name = "${local.name}-vpc"
cidr = local.vpc_cidr_blocks
azs = local.azs
private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr_blocks, 4, k)]
public_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr_blocks, 8, k + 48)]
public_subnet_suffix = "SubnetPublic"
private_subnet_suffix = "SubnetPrivate"
# Infrastructure Cost Management
enable_nat_gateway = true
single_nat_gateway = true
enable_dns_hostnames = true
enable_dns_support = true
public_subnet_tags = {
"kubernetes.io/role/elb" = 1
"Tier" = "Public"
}
private_subnet_tags = {
"kubernetes.io/role/internal-elb" = 1
"Tier" = "Private"
# Essential for Karpenter v1.0+ node provisioning
"karpenter.sh/discovery" = local.name
}
# Observability & Compliance
enable_flow_log = true
flow_log_destination_type = "cloud-watch-logs"
create_flow_log_cloudwatch_log_group = true
create_flow_log_cloudwatch_iam_role = true
flow_log_cloudwatch_log_group_retention_in_days = 7
# Detailed log format for deep network troubleshooting
flow_log_log_format = "$${interface-id} $${srcaddr} $${srcport} $${pkt-src-aws-service} $${dstaddr} $${dstport} $${pkt-dst-aws-service} $${protocol} $${flow-direction} $${traffic-path} $${action} $${log-status} $${subnet-id} $${az-id}"
tags = local.tags
}State Management: Enter HCP Terraform
In a professional environment, you never want to keep your terraform.tfstate file on your local machine. If your laptop dies or a teammate needs to make a change, you're in trouble.
For this guide, we're using HCP Terraform (formerly Terraform Cloud). It acts as our "Single Source of Truth," providing:
- Remote State Storage: Securely stores your state files.
- State Locking: Prevents two people from running updates at the same time (which can corrupt your infra).
- Audit History: See exactly who changed what and when.
- Terraform cloud configs and state management:
To get started, create a free account at HCP Terraform. Once you've created your organization and workspace, add this block to your configuration:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.47.0"
}
}
required_version = ">= 1.3.7"
cloud {
organization = "comprehensive-eks-guide"
workspaces {
name = "prod-vpc"
}
}
}terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.47.0"
}
}
required_version = ">= 1.3.7"
cloud {
organization = "comprehensive-eks-guide"
workspaces {
name = "prod-vpc"
}
}
}Provisioning the Foundation
With our networking logic and state management in place, it's time to pull the trigger. Open your terminal and run the following commands:
- Initialize
This prepares your working directory, downloads the AWS provider, and connects to your HCP Terraform workspace.
terraform initterraform init- The Execution Plan
Always check your work before you deploy. The plan command shows exactly what AWS resources will be created.
terraform plan -var-file=vars.tfvarsterraform plan -var-file=vars.tfvars- Apply the Changes
Once you've reviewed the plan and everything looks correct, deploy the infrastructure.
terraform apply -var-file=vars.tfvarsterraform apply -var-file=vars.tfvarsSummary & What's Next
We've successfully laid the groundwork. By following this guide, you now have a Production-Grade VPC featuring:
- Multi-AZ redundancy for high availability.
- Isolated private subnets for secure compute.
- Automated NAT Gateway routing for outbound connectivity.
- Essential tagging that Kubernetes requires to manage Load Balancers and scaling.
This networking layer is the "hard part" — with this foundation solid, we are ready for the main event.
In Part 2, we'll build on this foundation to provision the AWS EKS Cluster and set up our Managed Node Groups.
Let's Connect
Building infrastructure that scales shouldn't be a guessing game. I specialize in helping companies migrate to EKS and automate their cloud workflows, often reducing infrastructure management overhead by up to 40%.
If you're looking to modernize your stack or need a hand with your Kubernetes journey, let's talk!