Manage AWS EKS Add-ons

The aws:eks/addon:Addon resource, part of the Pulumi AWS provider, installs and manages AWS-provided add-ons that extend EKS cluster functionality. This guide focuses on three capabilities: installing add-ons with version control, preserving custom configuration during upgrades, and passing JSON configuration schemas.

Add-ons require an existing EKS cluster and may reference IAM roles or OIDC providers for service account permissions. The examples are intentionally small. Combine them with your own cluster infrastructure and IAM configuration.

Install a basic add-on to an existing cluster

Most clusters start by installing core add-ons like vpc-cni to provide networking capabilities.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.eks.Addon("example", {
    clusterName: exampleAwsEksCluster.name,
    addonName: "vpc-cni",
});
import pulumi
import pulumi_aws as aws

example = aws.eks.Addon("example",
    cluster_name=example_aws_eks_cluster["name"],
    addon_name="vpc-cni")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/eks"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := eks.NewAddon(ctx, "example", &eks.AddonArgs{
			ClusterName: pulumi.Any(exampleAwsEksCluster.Name),
			AddonName:   pulumi.String("vpc-cni"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Eks.Addon("example", new()
    {
        ClusterName = exampleAwsEksCluster.Name,
        AddonName = "vpc-cni",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.eks.Addon;
import com.pulumi.aws.eks.AddonArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Addon("example", AddonArgs.builder()
            .clusterName(exampleAwsEksCluster.name())
            .addonName("vpc-cni")
            .build());

    }
}
resources:
  example:
    type: aws:eks:Addon
    properties:
      clusterName: ${exampleAwsEksCluster.name}
      addonName: vpc-cni

The clusterName property references your EKS cluster, and addonName specifies which AWS-managed component to install. Without specifying addonVersion, AWS installs the default version for your cluster’s Kubernetes version.

Upgrade add-ons while preserving custom configuration

When you customize add-on behavior using kubectl after installation, you need to control whether AWS overwrites those changes during upgrades.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.eks.Addon("example", {
    clusterName: exampleAwsEksCluster.name,
    addonName: "coredns",
    addonVersion: "v1.10.1-eksbuild.1",
    resolveConflictsOnUpdate: "PRESERVE",
});
import pulumi
import pulumi_aws as aws

example = aws.eks.Addon("example",
    cluster_name=example_aws_eks_cluster["name"],
    addon_name="coredns",
    addon_version="v1.10.1-eksbuild.1",
    resolve_conflicts_on_update="PRESERVE")
package main

import (
	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/eks"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := eks.NewAddon(ctx, "example", &eks.AddonArgs{
			ClusterName:              pulumi.Any(exampleAwsEksCluster.Name),
			AddonName:                pulumi.String("coredns"),
			AddonVersion:             pulumi.String("v1.10.1-eksbuild.1"),
			ResolveConflictsOnUpdate: pulumi.String("PRESERVE"),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Eks.Addon("example", new()
    {
        ClusterName = exampleAwsEksCluster.Name,
        AddonName = "coredns",
        AddonVersion = "v1.10.1-eksbuild.1",
        ResolveConflictsOnUpdate = "PRESERVE",
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.eks.Addon;
import com.pulumi.aws.eks.AddonArgs;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Addon("example", AddonArgs.builder()
            .clusterName(exampleAwsEksCluster.name())
            .addonName("coredns")
            .addonVersion("v1.10.1-eksbuild.1")
            .resolveConflictsOnUpdate("PRESERVE")
            .build());

    }
}
resources:
  example:
    type: aws:eks:Addon
    properties:
      clusterName: ${exampleAwsEksCluster.name}
      addonName: coredns
      addonVersion: v1.10.1-eksbuild.1
      resolveConflictsOnUpdate: PRESERVE

The addonVersion property pins the add-on to a specific release. The resolveConflictsOnUpdate property with PRESERVE tells AWS to keep your kubectl-applied changes rather than overwriting them with add-on defaults. This lets you upgrade the add-on version without losing customizations.

Configure add-on behavior with custom JSON settings

Add-ons expose configuration schemas that control replica counts, resource limits, and other runtime behavior.

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const example = new aws.eks.Addon("example", {
    clusterName: "mycluster",
    addonName: "coredns",
    addonVersion: "v1.10.1-eksbuild.1",
    resolveConflictsOnCreate: "OVERWRITE",
    configurationValues: JSON.stringify({
        replicaCount: 4,
        resources: {
            limits: {
                cpu: "100m",
                memory: "150Mi",
            },
            requests: {
                cpu: "100m",
                memory: "150Mi",
            },
        },
    }),
});
import pulumi
import json
import pulumi_aws as aws

example = aws.eks.Addon("example",
    cluster_name="mycluster",
    addon_name="coredns",
    addon_version="v1.10.1-eksbuild.1",
    resolve_conflicts_on_create="OVERWRITE",
    configuration_values=json.dumps({
        "replicaCount": 4,
        "resources": {
            "limits": {
                "cpu": "100m",
                "memory": "150Mi",
            },
            "requests": {
                "cpu": "100m",
                "memory": "150Mi",
            },
        },
    }))
package main

import (
	"encoding/json"

	"github.com/pulumi/pulumi-aws/sdk/v7/go/aws/eks"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		tmpJSON0, err := json.Marshal(map[string]interface{}{
			"replicaCount": 4,
			"resources": map[string]interface{}{
				"limits": map[string]interface{}{
					"cpu":    "100m",
					"memory": "150Mi",
				},
				"requests": map[string]interface{}{
					"cpu":    "100m",
					"memory": "150Mi",
				},
			},
		})
		if err != nil {
			return err
		}
		json0 := string(tmpJSON0)
		_, err = eks.NewAddon(ctx, "example", &eks.AddonArgs{
			ClusterName:              pulumi.String("mycluster"),
			AddonName:                pulumi.String("coredns"),
			AddonVersion:             pulumi.String("v1.10.1-eksbuild.1"),
			ResolveConflictsOnCreate: pulumi.String("OVERWRITE"),
			ConfigurationValues:      pulumi.String(json0),
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using System.Text.Json;
using Pulumi;
using Aws = Pulumi.Aws;

return await Deployment.RunAsync(() => 
{
    var example = new Aws.Eks.Addon("example", new()
    {
        ClusterName = "mycluster",
        AddonName = "coredns",
        AddonVersion = "v1.10.1-eksbuild.1",
        ResolveConflictsOnCreate = "OVERWRITE",
        ConfigurationValues = JsonSerializer.Serialize(new Dictionary<string, object?>
        {
            ["replicaCount"] = 4,
            ["resources"] = new Dictionary<string, object?>
            {
                ["limits"] = new Dictionary<string, object?>
                {
                    ["cpu"] = "100m",
                    ["memory"] = "150Mi",
                },
                ["requests"] = new Dictionary<string, object?>
                {
                    ["cpu"] = "100m",
                    ["memory"] = "150Mi",
                },
            },
        }),
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.aws.eks.Addon;
import com.pulumi.aws.eks.AddonArgs;
import static com.pulumi.codegen.internal.Serialization.*;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;

public class App {
    public static void main(String[] args) {
        Pulumi.run(App::stack);
    }

    public static void stack(Context ctx) {
        var example = new Addon("example", AddonArgs.builder()
            .clusterName("mycluster")
            .addonName("coredns")
            .addonVersion("v1.10.1-eksbuild.1")
            .resolveConflictsOnCreate("OVERWRITE")
            .configurationValues(serializeJson(
                jsonObject(
                    jsonProperty("replicaCount", 4),
                    jsonProperty("resources", jsonObject(
                        jsonProperty("limits", jsonObject(
                            jsonProperty("cpu", "100m"),
                            jsonProperty("memory", "150Mi")
                        )),
                        jsonProperty("requests", jsonObject(
                            jsonProperty("cpu", "100m"),
                            jsonProperty("memory", "150Mi")
                        ))
                    ))
                )))
            .build());

    }
}
resources:
  example:
    type: aws:eks:Addon
    properties:
      clusterName: mycluster
      addonName: coredns
      addonVersion: v1.10.1-eksbuild.1
      resolveConflictsOnCreate: OVERWRITE
      configurationValues:
        fn::toJSON:
          replicaCount: 4
          resources:
            limits:
              cpu: 100m
              memory: 150Mi
            requests:
              cpu: 100m
              memory: 150Mi

The configurationValues property accepts a JSON string that matches the add-on’s schema. You can extract each add-on’s schema using the AWS CLI’s describe-addon-configuration command. Here, the JSON sets CoreDNS to run 4 replicas with specific CPU and memory limits. The resolveConflictsOnCreate property with OVERWRITE ensures your configuration takes precedence over any existing settings.

Beyond these examples

These snippets focus on specific add-on features: installation and version management, conflict resolution during updates, and custom configuration via JSON schemas. They’re intentionally minimal rather than full cluster deployments.

The examples reference pre-existing infrastructure such as EKS clusters, and IAM OIDC providers for serviceAccountRoleArn (not shown in examples). They focus on configuring the add-on rather than provisioning the cluster around it.

To keep things focused, common add-on patterns are omitted, including:

  • IAM role binding (serviceAccountRoleArn)
  • Pod Identity associations (podIdentityAssociations)
  • Preservation on deletion (preserve property)
  • Conflict resolution on creation (resolveConflictsOnCreate)

These omissions are intentional: the goal is to illustrate how each add-on feature is wired, not provide drop-in cluster modules. See the EKS Addon resource reference for all available configuration options.

Let's manage AWS EKS Add-ons

Get started with Pulumi Cloud, then follow our quick setup guide to deploy this infrastructure.

Try Pulumi Cloud for FREE

Frequently Asked Questions

Configuration & Conflicts
How do I retain my kubectl configuration changes when upgrading an addon?
Set resolveConflictsOnUpdate to PRESERVE to keep configuration changes applied with kubectl during version upgrades.
What's the difference between resolveConflictsOnCreate and resolveConflictsOnUpdate?
resolveConflictsOnCreate handles conflicts when migrating a self-managed add-on (valid values: NONE, OVERWRITE), while resolveConflictsOnUpdate handles conflicts during add-on updates (valid values: NONE, OVERWRITE, PRESERVE).
How do I find the correct JSON schema for configurationValues?
Use the AWS CLI command aws eks describe-addon-configuration --addon-name <name> --addon-version <version> to extract the schema for your specific add-on and version.
IAM & Permissions
Why can't I specify a custom IAM role for my addon?
You must have an IAM OpenID Connect (OIDC) provider created for your cluster before specifying a serviceAccountRoleArn.
What happens if I don't specify a serviceAccountRoleArn?
The add-on uses the permissions assigned to the node IAM role by default.
Versioning & Updates
How do I find valid addon versions for my cluster?
Use the AWS CLI command describe-addon-versions to list available versions for each add-on.
What happens if my configurationValues JSON doesn't match the addon schema?
The configurationValues JSON string must match the valid JSON schema for your specific add-on and version, or the operation will fail.
Resource Management
Which properties can't be changed after creating an addon?
Both addonName and clusterName are immutable and cannot be changed after creation.
How do I keep addon resources when deleting the addon?
Set preserve to true to retain the created resources when deleting the EKS add-on.

Using a different cloud?

Explore containers guides for other cloud providers: