Skip to main content
Self-managed node groups give you complete control over the EC2 instances that join your EKS cluster. Unlike EKS managed node groups, the entire Auto Scaling Group lifecycle — including AMI selection, bootstrap configuration, and instance replacement — is your responsibility. Refer to the AWS self-managed nodes documentation for service-level details.

How it works

The self-managed-node-group submodule creates:
  • An EC2 launch template
  • An Auto Scaling Group (ASG) using that launch template
  • An IAM instance profile and role for the nodes
  • An EKS access entry so nodes can join the cluster
  • A security group for the node group
By default, the module uses the latest AWS EKS Optimized AMI for the given Kubernetes version and AMI type.

Basic configuration

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.0"

  name               = "my-cluster"
  kubernetes_version = "1.33"

  self_managed_node_groups = {
    # Uses the latest EKS Optimized AMI for Kubernetes 1.33 automatically
    default = {}
  }

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  tags = {
    Environment = "dev"
    Terraform   = "true"
  }
}

Examples

module "eks" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.0"

  name               = "my-cluster"
  kubernetes_version = "1.33"

  addons = {
    coredns = {}
    eks-pod-identity-agent = {
      before_compute = true
    }
    kube-proxy = {}
    vpc-cni = {
      before_compute = true
    }
  }

  vpc_id     = module.vpc.vpc_id
  subnet_ids = module.vpc.private_subnets

  self_managed_node_groups = {
    example = {
      ami_type      = "AL2023_x86_64_STANDARD"
      instance_type = "m6i.large"

      min_size     = 2
      max_size     = 5
      desired_size = 2

      # Optional: pass additional nodeadm configuration
      cloudinit_pre_nodeadm = [
        {
          content_type = "application/node.eks.aws"
          content      = <<-EOT
            ---
            apiVersion: node.eks.aws/v1alpha1
            kind: NodeConfig
            spec:
              kubelet:
                config:
                  shutdownGracePeriod: 30s
          EOT
        }
      ]
    }
  }

  tags = {
    Environment = "dev"
    Terraform   = "true"
  }
}

Auto Scaling Group configuration

The module creates an Auto Scaling Group with a rolling instance refresh strategy by default:
# Default instance_refresh configuration
instance_refresh = {
  strategy = "Rolling"
  preferences = {
    min_healthy_percentage = 66
  }
}
You can override any ASG-level settings at the node group level:
self_managed_node_groups = {
  example = {
    instance_type = "m6i.large"
    min_size      = 2
    max_size      = 10
    desired_size  = 2

    # Protect nodes from scale-in during drain
    protect_from_scale_in = true

    # Replace nodes after 7 days maximum lifetime
    max_instance_lifetime = 604800

    # Custom instance refresh settings
    instance_refresh = {
      strategy = "Rolling"
      preferences = {
        min_healthy_percentage = 75
        instance_warmup        = 300
      }
    }
  }
}

Mixed instances policy

For cost optimization using a blend of On-Demand and Spot instances, enable use_mixed_instances_policy:
self_managed_node_groups = {
  mixed = {
    min_size     = 2
    max_size     = 10
    desired_size = 4

    use_mixed_instances_policy = true
    mixed_instances_policy = {
      instances_distribution = {
        on_demand_base_capacity                  = 1
        on_demand_percentage_above_base_capacity = 25
        spot_allocation_strategy                 = "capacity-optimized"
      }
      launch_template = {
        override = [
          { instance_type = "m6i.large" },
          { instance_type = "m5.large" },
          { instance_type = "m5a.large" },
        ]
      }
    }
  }
}

Launch template options

The module creates a launch template for each self-managed node group. All standard EC2 launch template options are exposed as variables:
self_managed_node_groups = {
  example = {
    instance_type = "m6i.large"

    # EBS root volume customization
    block_device_mappings = {
      xvda = {
        device_name = "/dev/xvda"
        ebs = {
          volume_size           = 50
          volume_type           = "gp3"
          encrypted             = true
          delete_on_termination = true
        }
      }
    }

    # IMDSv2 required (module default)
    metadata_options = {
      http_endpoint               = "enabled"
      http_tokens                 = "required"
      http_put_response_hop_limit = 2
    }

    # Detailed CloudWatch monitoring
    enable_monitoring = true
  }
}

Bootstrap user data

For AL2023 nodes, user data is configured using cloud-init multi-part documents with nodeadm:
self_managed_node_groups = {
  example = {
    ami_type      = "AL2023_x86_64_STANDARD"
    instance_type = "m6i.large"

    # Injected before the nodeadm bootstrap document
    cloudinit_pre_nodeadm = [{
      content_type = "application/node.eks.aws"
      content      = <<-EOT
        ---
        apiVersion: node.eks.aws/v1alpha1
        kind: NodeConfig
        spec:
          kubelet:
            flags:
              - --node-labels=node.kubernetes.io/lifecycle=spot
      EOT
    }]

    # Injected after the nodeadm bootstrap document
    cloudinit_post_nodeadm = [{
      content_type = "text/x-shellscript; charset=\"us-ascii\""
      content      = <<-EOT
        #!/bin/bash
        echo "Bootstrap complete" | tee /var/log/bootstrap-done.txt
      EOT
    }]
  }
}

IAM and access entry

The module creates an IAM instance profile, an IAM role with the required EKS node policies, and an EKS access entry (create_access_entry = true by default). The access entry allows the node role to join the cluster without configuring the aws-auth ConfigMap. To use an existing IAM role:
self_managed_node_groups = {
  example = {
    instance_type = "m6i.large"

    create_iam_instance_profile = false
    iam_instance_profile_arn    = "arn:aws:iam::123456789012:instance-profile/my-node-profile"
  }
}

Key variables reference

VariableDefaultDescription
instance_typem6i.largeEC2 instance type
ami_typeAL2023_x86_64_STANDARDAMI family (determines bootstrap behavior)
ami_id""Custom AMI ID; leave empty to use the latest EKS-optimized AMI
min_size1ASG minimum capacity
max_size3ASG maximum capacity
desired_size1ASG initial desired capacity
use_mixed_instances_policyfalseEnable mixed On-Demand and Spot instances
create_access_entrytrueCreate EKS access entry for the node role
enable_efa_supportfalseEnable EFA networking interfaces

Build docs developers (and LLMs) love