Manage AWS EKS Add-ons

The aws:eks/addon:Addon resource, part of the Pulumi AWS provider, installs and manages AWS-provided add-ons (vpc-cni, CoreDNS, kube-proxy) on EKS clusters. This guide focuses on three capabilities: basic installation, version upgrades with conflict resolution, and JSON-based configuration.

Add-ons attach to existing EKS clusters. Some add-ons require IAM OIDC providers for service account roles. The examples are intentionally small. Combine them with your own cluster infrastructure and IAM configuration.

Install a basic add-on to your cluster

Most clusters start by installing essential add-ons like vpc-cni for pod networking.

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. The addonName property specifies which add-on to install; valid names come from the describe-addon-versions AWS CLI command. Without specifying addonVersion, AWS installs the default version for your cluster.

Preserve custom configuration during upgrades

Teams often customize add-ons with kubectl after installation. When upgrading, you can preserve those changes rather than reverting to defaults.

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 version. The resolveConflictsOnUpdate property with PRESERVE keeps any kubectl-applied changes when upgrading. Without this, upgrades overwrite customizations with AWS defaults.

Configure add-on settings with JSON values

Add-ons expose configuration schemas for replica counts, resource limits, and 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 matching the add-on’s schema. Use the describe-addon-configuration AWS CLI command to discover valid settings. The resolveConflictsOnCreate property with OVERWRITE replaces any existing configuration when migrating self-managed add-ons.

Beyond these examples

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

The examples reference pre-existing infrastructure such as EKS clusters and IAM OIDC providers (for service account roles). They focus on configuring the add-on rather than provisioning the cluster.

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

  • IAM service account roles (serviceAccountRoleArn)
  • Pod Identity associations (podIdentityAssociations)
  • Preservation on deletion (preserve property)
  • Tags for cost tracking and organization

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 & Customization
How do I customize my EKS addon configuration?
Use the configurationValues property with a JSON string that matches your addon’s schema. The JSON must be valid for your specific addon name and version.
How do I find the correct JSON schema for my addon configuration?
Run aws eks describe-addon-configuration --addon-name <name> --addon-version <version> to extract the JSON schema. You can pipe the output through jq to format it.
How do I find valid addon names and versions for my cluster?
Use the AWS CLI command describe-addon-versions to list available addon names and their compatible versions for your EKS cluster.
Conflict Resolution & Updates
How can I keep my kubectl changes when upgrading an addon?
Set resolveConflictsOnUpdate to PRESERVE to retain configuration changes you’ve applied with kubectl during addon version upgrades.
What are the conflict resolution options when creating an addon?
Use resolveConflictsOnCreate with either NONE or OVERWRITE to control how field conflicts are handled when migrating a self-managed addon to an Amazon EKS addon.
What are the conflict resolution options when updating an addon?
Use resolveConflictsOnUpdate with NONE, OVERWRITE, or PRESERVE to control how field value conflicts are resolved when updating an addon.
IAM & Permissions
What's required to use a custom IAM role with my addon?
You must have an IAM OpenID Connect (OIDC) provider created for your cluster before specifying serviceAccountRoleArn. Without an OIDC provider, the addon will use the node IAM role permissions instead.
What happens if I don't specify a serviceAccountRoleArn?
The addon will use the permissions assigned to the node IAM role instead of a dedicated service account role.
Immutability & Lifecycle
What properties can't I change after creating an addon?
Both addonName and clusterName are immutable and cannot be changed after the addon is created. You’ll need to recreate the addon to change these values.
What happens to addon resources when I delete the addon?
Set preserve to true if you want to keep the resources created by the addon when deleting it. Otherwise, the resources will be removed.

Using a different cloud?

Explore containers guides for other cloud providers: