Deploy Kubernetes Resources with Kustomize Directories

The kubernetes:kustomize:Directory resource, part of the Pulumi Kubernetes provider, deploys a collection of Kubernetes resources defined by a kustomization directory. A newer version is available as kubernetes.kustomize/v2.Directory. This guide focuses on three capabilities: local directory deployment, remote Git repository sources, and runtime resource transformations.

Kustomize directories require a Kubernetes cluster, valid kustomization.yaml files, and the kustomize binary. The examples are intentionally small. Combine them with your own cluster configuration and resource definitions.

Deploy resources from a local kustomization directory

Most deployments start with a local directory containing a kustomization.yaml file and the manifests it references.

import * as k8s from "@pulumi/kubernetes";

const helloWorld = new k8s.kustomize.Directory("helloWorldLocal", {
    directory: "./helloWorld",
});
from pulumi_kubernetes.kustomize import Directory

hello_world = Directory(
    "hello-world-local",
    directory="./helloWorld",
)
package main

import (
	"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/kustomize"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := kustomize.NewDirectory(ctx, "helloWorldLocal",
			kustomize.DirectoryArgs{
				Directory: pulumi.String("./helloWorld"),
			},
		)
		if err != nil {
			return err
		}

		return nil
	})
}
using System.Threading.Tasks;
using Pulumi;
using Pulumi.Kubernetes.Kustomize;

class KustomizeStack : Stack
{
    public KustomizeStack()
    {
        var helloWorld = new Directory("helloWorldLocal", new DirectoryArgs
        {
            Directory = "./helloWorld",
        });
    }
}

The directory property points to a local path containing your kustomization.yaml. Pulumi runs kustomize to process the directory, then applies the resulting manifests to your cluster. This works well during development when resources live alongside your Pulumi code.

Deploy resources from a remote Git repository

Teams often store kustomizations in Git to share configurations or reference upstream examples.

import * as k8s from "@pulumi/kubernetes";

const helloWorld = new k8s.kustomize.Directory("helloWorldRemote", {
    directory: "https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld",
});
from pulumi_kubernetes.kustomize import Directory

hello_world = Directory(
    "hello-world-remote",
    directory="https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld",
)
package main

import (
	"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/kustomize"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := kustomize.NewDirectory(ctx, "helloWorldRemote",
			kustomize.DirectoryArgs{
				Directory: pulumi.String("https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld"),
			},
		)
		if err != nil {
			return err
		}

		return nil
	})
}
using System.Threading.Tasks;
using Pulumi;
using Pulumi.Kubernetes.Kustomize;

class KustomizeStack : Stack
{
    public KustomizeStack()
    {
        var helloWorld = new Directory("helloWorldRemote", new DirectoryArgs
        {
            Directory = "https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld",
        });
    }
}

The directory property accepts Git URLs with tree references. Pulumi fetches the repository, processes the kustomization at that path, and applies the resources. The example pins to a specific version (v3.3.1) rather than tracking a branch.

Modify resources with transformation functions

Applications often need to adjust manifests before deployment, such as changing service types or setting resource aliases.

import * as k8s from "@pulumi/kubernetes";

const helloWorld = new k8s.kustomize.Directory("helloWorldRemote", {
    directory: "https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld",
    transformations: [
        // Make every service private to the cluster, i.e., turn all services into ClusterIP instead of LoadBalancer.
        (obj: any, opts: pulumi.CustomResourceOptions) => {
            if (obj.kind === "Service" && obj.apiVersion === "v1") {
                if (obj.spec && obj.spec.type && obj.spec.type === "LoadBalancer") {
                    obj.spec.type = "ClusterIP";
                }
            }
        },

        // Set a resource alias for a previous name.
        (obj: any, opts: pulumi.CustomResourceOptions) => {
            if (obj.kind === "Deployment") {
                opts.aliases = [{ name: "oldName" }]
            }
        },

        // Omit a resource from the Chart by transforming the specified resource definition to an empty List.
        (obj: any, opts: pulumi.CustomResourceOptions) => {
            if (obj.kind === "Pod" && obj.metadata.name === "test") {
                obj.apiVersion = "v1"
                obj.kind = "List"
            }
        },
    ],
});
from pulumi_kubernetes.helm.v3 import Chart, ChartOpts, FetchOpts

# Make every service private to the cluster, i.e., turn all services into ClusterIP instead of LoadBalancer.
def make_service_private(obj, opts):
    if obj["kind"] == "Service" and obj["apiVersion"] == "v1":
        try:
            t = obj["spec"]["type"]
            if t == "LoadBalancer":
                obj["spec"]["type"] = "ClusterIP"
        except KeyError:
            pass


# Set a resource alias for a previous name.
def alias(obj, opts):
    if obj["kind"] == "Deployment":
        opts.aliases = ["oldName"]


# Omit a resource from the Chart by transforming the specified resource definition to an empty List.
def omit_resource(obj, opts):
    if obj["kind"] == "Pod" and obj["metadata"]["name"] == "test":
        obj["apiVersion"] = "v1"
        obj["kind"] = "List"


hello_world = Directory(
    "hello-world-remote",
    directory="https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld",
    transformations=[make_service_private, alias, omit_resource],
)
package main

import (
	"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/kustomize"
	"github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/yaml"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := kustomize.NewDirectory(ctx, "helloWorldRemote",
			kustomize.DirectoryArgs{
				Directory: pulumi.String("https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld"),
				Transformations: []yaml.Transformation{
					// Make every service private to the cluster, i.e., turn all services into ClusterIP
					// instead of LoadBalancer.
					func(state map[string]interface{}, opts ...pulumi.ResourceOption) {
						if state["kind"] == "Service" {
							spec := state["spec"].(map[string]interface{})
							spec["type"] = "ClusterIP"
						}
					},

					// Set a resource alias for a previous name.
					func(state map[string]interface{}, opts ...pulumi.ResourceOption) {
						if state["kind"] == "Deployment" {
							aliases := pulumi.Aliases([]pulumi.Alias{
								{
									Name: pulumi.String("oldName"),
								},
							})
							opts = append(opts, aliases)
						}
					},

					// Omit a resource from the Chart by transforming the specified resource definition
					// to an empty List.
					func(state map[string]interface{}, opts ...pulumi.ResourceOption) {
						name := state["metadata"].(map[string]interface{})["name"]
						if state["kind"] == "Pod" && name == "test" {
							state["apiVersion"] = "core/v1"
							state["kind"] = "List"
						}
					},
				},
			},
		)
		if err != nil {
			return err
		}

		return nil
	})
}
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks;
using Pulumi;
using Pulumi.Kubernetes.Kustomize;

class KustomizeStack : Stack
{
    public KustomizeStack()
    {
        var helloWorld = new Directory("helloWorldRemote", new DirectoryArgs
        {
            Directory = "https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld",
            Transformations =
              {
                  LoadBalancerToClusterIP,
                  ResourceAlias,
                  OmitTestPod,
              }
        });

        // Make every service private to the cluster, i.e., turn all services into ClusterIP instead of LoadBalancer.
        ImmutableDictionary<string, object> LoadBalancerToClusterIP(ImmutableDictionary<string, object> obj, CustomResourceOptions opts)
        {
            if ((string)obj["kind"] == "Service" && (string)obj["apiVersion"] == "v1")
            {
                var spec = (ImmutableDictionary<string, object>)obj["spec"];
                if (spec != null && (string)spec["type"] == "LoadBalancer")
                {
                    return obj.SetItem("spec", spec.SetItem("type", "ClusterIP"));
                }
            }

            return obj;
        }

        // Set a resource alias for a previous name.
        ImmutableDictionary<string, object> ResourceAlias(ImmutableDictionary<string, object> obj, CustomResourceOptions opts)
        {
            if ((string)obj["kind"] == "Deployment")
            {
                opts.Aliases.Add(new Alias { Name = "oldName" });
            }

            return obj;
        }

        // Omit a resource from the Chart by transforming the specified resource definition to an empty List.
        ImmutableDictionary<string, object> OmitTestPod(ImmutableDictionary<string, object> obj, CustomResourceOptions opts)
        {
            var metadata = (ImmutableDictionary<string, object>)obj["metadata"];
            if ((string)obj["kind"] == "Pod" && (string)metadata["name"] == "test")
            {
                return new Dictionary<string, object>
                {
                    ["apiVersion"] = "v1",
                    ["kind"] = "List",
                    ["items"] = new Dictionary<string, object>(),
                }.ToImmutableDictionary();
            }

            return obj;
        }
    }
}

The transformations property accepts an array of functions that receive each resource definition before it’s created. Each function can inspect the resource’s kind and apiVersion, then modify properties or set Pulumi options. The example shows three common patterns: changing service types, setting aliases for migrations, and omitting specific resources.

Beyond these examples

These snippets focus on specific Directory features: local and remote directory sources, and runtime resource transformations. They’re intentionally minimal rather than full application deployments.

The examples assume pre-existing infrastructure such as a Kubernetes cluster with configured kubeconfig, and kustomization.yaml files in referenced directories. They focus on deploying kustomizations rather than creating the underlying cluster or manifests.

To keep things focused, common Directory patterns are omitted, including:

  • Resource name prefixing (resourcePrefix)
  • Namespace targeting and isolation
  • Dependency ordering between resources
  • Output access to created resources

These omissions are intentional: the goal is to illustrate how each Directory feature is wired, not provide drop-in deployment modules. See the Kustomize Directory resource reference for all available configuration options.

Let's deploy Kubernetes Resources with Kustomize Directories

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

Try Pulumi Cloud for FREE

Frequently Asked Questions

Version & Migration
Should I use this version or the v2 Directory resource?
A newer version is available as kubernetes.kustomize/v2.Directory. Consider using v2 for new projects.
Directory Sources
Can I use a Git repository instead of a local directory?
Yes, the directory property accepts both local paths (like ./helloWorld) and Git repository URLs (like https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld).
What format should I use for Git repository URLs?
Use the full GitHub URL including the tree path and version, for example: https://github.com/kubernetes-sigs/kustomize/tree/v3.3.1/examples/helloWorld.
Resource Customization
What can I do with transformations?
Transformations let you modify Kubernetes resources before they’re created. Common uses include changing service types (like converting LoadBalancer to ClusterIP), setting resource aliases, or omitting specific resources.
How do I exclude a specific resource from being created?
Use a transformation to convert the resource to an empty List by setting apiVersion to "v1" and kind to "List". The example shows omitting a Pod named “test” this way.
How do I add a prefix to all resource names?
Set the resourcePrefix property. For example, resourcePrefix="foo" will produce resource names like “foo-resourceName”.