Terraform and Amazon EKS
The Terraform EKS module: what it builds, and what is still yours to do
terraform-aws-modules/eks/aws is the standard way to create an Amazon EKS cluster with Terraform. One apply gives you a control plane, add-ons and nodes. It does not give you ingress, DNS, certificates or a process for upgrades, and those are the parts that take the most time after the first cluster is up.
By Muhammad Soliman, Founder at SeaGit
·
Key takeaways
- The module creates the EKS control plane, its add-ons, access entries and, if you ask, managed node groups or Auto Mode compute. It does not create a VPC.
- Module version 21 uses name, kubernetes_version and addons. Examples written for older majors use other input names, so check the version line before you copy one.
- EKS gives each Kubernetes minor version 14 months of standard support, then 12 months of extended support at a higher hourly rate. Plan an upgrade inside that window.
- Ingress, DNS, TLS and application delivery are separate layers. The module leaves them to you, and an apply that succeeds does not mean an application has a working URL.
- SeaGit creates EKS clusters in your own AWS account. It is AWS-only today, so Azure and GCP clusters need the module or another platform.
What the module creates
The module creates the EKS cluster, which is the control plane that AWS operates. It creates the add-ons you list under addons, such as vpc-cni, kube-proxy and coredns, as managed EKS add-ons. It creates access entries from access_entries and the cluster creator permission when you enable it. If you set eks_managed_node_groups, it creates EC2 Auto Scaling groups and the node IAM roles. If you set compute_config with enabled set to true, it turns on EKS Auto Mode instead.
It does not create a VPC, subnets or route tables. It does not install a load balancer controller, an ingress controller, external-dns or cert-manager. Those are cluster add-ons in the Kubernetes sense, and you install them after the module finishes. kubectl works as soon as apply completes. A public URL for an application does not, until you add those layers.
A working example on module v21
The module is published on the Terraform Registry and developed on GitHub. The example below uses the input names from its v21 usage examples. The VPC is passed in as variables, so the example does not depend on how you build your network. Add-ons with before_compute are created before the node group, which the module’s examples use for vpc-cni and the pod identity agent.
module "eks" {
source = "terraform-aws-modules/eks/aws"
version = "~> 21.0"
name = "my-cluster"
kubernetes_version = "1.33"
vpc_id = var.vpc_id
subnet_ids = var.private_subnet_ids
endpoint_public_access = true
# Adds the identity running Terraform as an administrator,
# through an access entry.
enable_cluster_creator_admin_permissions = true
addons = {
coredns = {}
kube-proxy = {}
eks-pod-identity-agent = { before_compute = true }
vpc-cni = { before_compute = true }
}
eks_managed_node_groups = {
default = {
ami_type = "AL2023_x86_64_STANDARD"
instance_types = ["m5.large"]
min_size = 2
max_size = 6
desired_size = 2
}
}
tags = {
Environment = "production"
Terraform = "true"
}
}Older examples use different names, such as cluster_name for the cluster and cluster_version for the Kubernetes version. Those names belong to earlier major versions. Check the version constraint first. A v21 plan run against a v19-era block fails on unknown arguments, and that failure is the cheap outcome.
Who can use kubectl
Access entries are the EKS-native way to map IAM principals to Kubernetes permissions. The enable_cluster_creator_admin_permissions input adds the identity that runs Terraform as an administrator, so the first apply leaves you a way in. Everyone else should get a narrower entry, scoped to the namespaces they work in.
access_entries = {
viewer = {
principal_arn = "arn:aws:iam::123456789012:role/eks-viewer"
policy_associations = {
view = {
policy_arn = "arn:aws:eks::aws:cluster-access-policy/AmazonEKSViewPolicy"
access_scope = {
namespaces = ["web"]
type = "namespace"
}
}
}
}
}The module’s documentation describes authentication_mode set to API_AND_CONFIG_MAP, which accepts both access entries and the aws-auth ConfigMap. Access entries created for node roles are managed for you. Entries for people are yours to keep current, and removing a person means removing their entry.
Kubernetes versions and what they cost
Every Kubernetes minor version on EKS has a fixed lifecycle. It gets 14 months of standard support, then 12 months of extended support. The AWS pricing page lists standard support at $0.10 per cluster per hour and extended support at $0.60 per cluster per hour, which includes the standard rate, as of October 2026. A cluster left on an old minor version costs more every hour, and after extended support it has no further security patches.
Upgrading the cluster
An upgrade is a change to kubernetes_version followed by a normal plan and apply. Review the plan before you apply it, because the node groups and add-ons that follow the cluster version can be replaced in the same run. Upgrade one minor version at a time. Skipping a minor version is not supported by EKS, so a jump from 1.31 to 1.33 is two upgrades.
# Confirm the control plane version after an apply
aws eks describe-cluster --name my-cluster --query "cluster.version" --output text
# The usual cycle for a version change
terraform plan -out=upgrade.tfplan
terraform apply upgrade.tfplanBefore the change, read the release notes for the target minor version. Each one lists removed APIs and behaviour changes. Run your manifests against those lists before the apply, not after.
What the module leaves to you
The module is scoped to the cluster, and it does that well. The rest is real work, and it is where most of the setup time goes.
Node sizing and scaling. A managed node group keeps a fixed instance type and a size range you set. Pod-driven scaling needs Karpenter or the Cluster Autoscaler, or Auto Mode, installed and configured separately.
Ingress and load balancing. Something must receive traffic and route it to services. That is a controller and a load balancer, and the choice of controller matters for future migrations. See the ingress end-of-life guide on this site for the current state of ingress-nginx.
DNS and TLS. Records for each hostname, and certificates for each one, are created outside the module. Decide who owns the zone and how certificates renew before the first public service goes live.
Workload identity. The example installs the pod identity agent, but the associations that give each workload its IAM role are separate resources. Create them per workload, and keep node roles narrow.
Observability and backup. Logs, metrics and alerts are not part of the module. Neither is a backup of cluster state or of the volumes your workloads use.
What to check after the first apply
A successful apply only means that AWS accepted the resources. Three checks show whether the cluster can run something. First, the managed nodes should join and report Ready. Second, the core add-ons should be running, because coredns cannot schedule until nodes exist, and the vpc-cni add-on has to be ready before nodes can get pod addresses. The example creates vpc-cni and the pod identity agent before the node group for that reason. Third, a second person should be able to reach the cluster through their own access entry, not through the identity that created it.
kubectl get nodes -o wide
kubectl get pods --namespace kube-system
aws eks list-addons --cluster-name my-clusterNodes that never reach Ready usually point to one of three causes: the node role is not allowed to join, the subnets have no route for the node to reach the EKS endpoint and pull images, or the add-ons did not start. Read the node group’s health issues in the EKS console or with the AWS CLI before you change the Terraform code, so you know which of the three you are fixing.
Keep the first apply small. Create the cluster and one node group, confirm the checks above, and only then add the controllers and the workloads. A failure in one of those layers is then easy to locate, because the earlier layers were known to work.
Keeping the module current
The version constraint in the example, ~> 21.0, accepts any 21.x release and blocks version 22. A minor or patch release is usually an update you can apply with a plan review. A new major version changes input names and defaults, and it needs the upgrade notes the module publishes for that major version before you change the constraint.
Pin the module version in the lock file and commit it, so a later plan on another machine uses the same module code. Review the plan for the cluster and node groups after each change, and treat a replacement of the cluster or of the node group as a change that needs a maintenance window, not a routine update.
Module or platform: how to choose
Use the module when your team wants to own the cluster and the layers above it, and has people who will keep those layers current. The module is versioned, documented and widely used, so the parts you write are the ones that are specific to you.
A platform fits when the cluster is one part of a larger job, and the team would rather not run the ingress, DNS, certificate and upgrade cycle itself. The trade-off is that the platform’s choices become yours too. Ask what it manages, where the cluster lives, and how you leave if you need to.
How SeaGit handles this
SeaGit creates Amazon EKS clusters inside your own AWS account, and the cluster belongs to that account. It sets up the cluster layers that this article lists as yours, including an ingress option, so you can run an application without writing those pieces yourself. The cluster and its settings are documented on the clusters page.
The limits are the same as everywhere in this article. SeaGit supports AWS and Amazon EKS only. If your target is Azure, GCP or a self-managed Kubernetes cluster, the module and the layers above it are your path.