> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/terraform-aws-modules/terraform-aws-eks/llms.txt
> Use this file to discover all available pages before exploring further.

# Self-Managed Node Group

> Deploy an EKS cluster with self-managed EC2 node groups, giving full control over the Auto Scaling group configuration.

Self-managed node groups give you complete control over the underlying EC2 Auto Scaling groups. Unlike EKS managed node groups, AWS does not manage the node lifecycle — you are responsible for draining and terminating nodes during upgrades. In return you gain access to configuration options that are not available with managed node groups, such as mixed instance policies, custom launch templates, and full userdata control.

This example demonstrates two clusters: one with Amazon Linux 2023 (AL2023) nodes and one with Bottlerocket nodes, both running as self-managed node groups.

## Prerequisites

* AWS credentials with permissions to create EKS, EC2 (including Auto Scaling), IAM, and VPC resources
* Terraform >= 1.5.7
* AWS provider >= 6.28
* The example provisions its own VPC — no pre-existing VPC is required

<Note>
  Self-managed node groups require the module to create an access entry on behalf of the node IAM role so that nodes can join the cluster. This is handled automatically by the module — no `aws-auth` ConfigMap changes are needed.
</Note>

## VPC and shared locals

Both clusters share a common VPC defined in `main.tf`.

```hcl theme={null}
provider "aws" {
  region = local.region
}

data "aws_availability_zones" "available" {
  # Exclude local zones
  filter {
    name   = "opt-in-status"
    values = ["opt-in-not-required"]
  }
}

locals {
  name   = "ex-self-mng"
  region = "eu-west-1"

  vpc_cidr = "10.0.0.0/16"
  azs      = slice(data.aws_availability_zones.available.names, 0, 3)

  tags = {
    Example    = local.name
    GithubRepo = "terraform-aws-eks"
    GithubOrg  = "terraform-aws-modules"
  }
}

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 6.0"

  name = local.name
  cidr = local.vpc_cidr

  azs             = local.azs
  private_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 4, k)]
  public_subnets  = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 48)]
  intra_subnets   = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 52)]

  enable_nat_gateway = true
  single_nat_gateway = true

  public_subnet_tags = {
    "kubernetes.io/role/elb" = 1
  }

  private_subnet_tags = {
    "kubernetes.io/role/internal-elb" = 1
  }

  tags = local.tags
}
```

## Amazon Linux 2023 cluster (`eks-al2023.tf`)

```hcl theme={null}
module "eks_al2023" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.0"

  name               = "${local.name}-al2023"
  kubernetes_version = "1.33"

  # EKS Addons
  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
      # This value is ignored after the initial creation
      # https://github.com/bryantbiggs/eks-desired-size-hack
      desired_size = 2

      # This is not required - demonstrates how to pass additional configuration to nodeadm
      # Ref https://awslabs.github.io/amazon-eks-ami/nodeadm/doc/api/
      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 = local.tags
}
```

## Bottlerocket cluster (`eks-bottlerocket.tf`)

```hcl theme={null}
module "eks_bottlerocket" {
  source  = "terraform-aws-modules/eks/aws"
  version = "~> 21.0"

  name               = "${local.name}-bottlerocket"
  kubernetes_version = "1.33"

  # EKS Addons
  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      = "BOTTLEROCKET_x86_64"
      instance_type = "m6i.large"

      min_size = 2
      max_size = 5
      # This value is ignored after the initial creation
      # https://github.com/bryantbiggs/eks-desired-size-hack
      desired_size = 2

      # This is not required - demonstrates how to pass additional configuration
      # Ref https://bottlerocket.dev/en/os/1.19.x/api/settings/
      bootstrap_extra_args = <<-EOT
        # The admin host container provides SSH access and runs with "superpowers".
        # It is disabled by default, but can be disabled explicitly.
        [settings.host-containers.admin]
        enabled = false

        # The control host container provides out-of-band access via SSM.
        # It is enabled by default, and can be disabled if you do not expect to use SSM.
        # This could leave you with no way to access the API and change settings on an existing node!
        [settings.host-containers.control]
        enabled = true

        # extra args added
        [settings.kernel]
        lockdown = "integrity"
      EOT
    }
  }

  tags = local.tags
}
```

## Self-managed vs. EKS managed: key differences

| Aspect | Self-managed | EKS managed |
| - | - | - |
| Node lifecycle (drain/terminate) | You manage | AWS manages |
| Instance type | Single `instance_type` | List of `instance_types` |
| Mixed instance policy | Supported | Not supported |
| Custom AMI | Fully supported | Supported with constraints |
| Access entry | Module creates automatically | AWS creates automatically |

### `instance_type` vs. `instance_types`

Self-managed node groups use a single `instance_type` string, whereas EKS managed node groups accept a list (`instance_types`). This is an important distinction when copying configuration between the two.

### Node upgrades

For self-managed node groups, node upgrades require you to:

1. Update the `kubernetes_version` (or `ami_release_version`) in Terraform
2. Apply the change to update the launch template
3. Manually drain and terminate old nodes (or use an instance refresh policy)

<Warning>
  Self-managed node group upgrades are not automated. Failing to drain nodes before termination can cause workload disruption.
</Warning>

## Deploy

<Steps>
  <Step title="Initialize Terraform">
    ```bash theme={null}
    terraform init
    ```
  </Step>

  <Step title="Review the plan">
    ```bash theme={null}
    terraform plan
    ```
  </Step>

  <Step title="Apply">
    ```bash theme={null}
    terraform apply
    ```
  </Step>

  <Step title="Configure kubectl">
    ```bash theme={null}
    # For the AL2023 cluster
    aws eks update-kubeconfig --region eu-west-1 --name ex-self-mng-al2023

    # For the Bottlerocket cluster
    aws eks update-kubeconfig --region eu-west-1 --name ex-self-mng-bottlerocket
    ```
  </Step>
</Steps>

## Key outputs

| Output | Description |
| - | - |
| `cluster_name` | Name of the EKS cluster |
| `cluster_endpoint` | Kubernetes API server endpoint |
| `self_managed_node_groups` | Map of attributes for all self-managed node groups |
| `self_managed_node_groups_autoscaling_group_names` | Auto Scaling group names for each node group |

## Full example on GitHub

[View the complete example](https://github.com/terraform-aws-modules/terraform-aws-eks/tree/master/examples/self-managed-node-group) including `outputs.tf`, `variables.tf`, and `versions.tf`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.