Create a Kubernetes Deployment

The kubernetes:apps/v1:Deployment resource, part of the Pulumi Kubernetes provider, defines a Kubernetes Deployment that manages replicated pods, ensuring the desired replica count and handling rolling updates. This guide focuses on two capabilities: pod replication with label-based selection, and resource naming strategies.

A Deployment doesn’t stand alone. It requires a Kubernetes cluster with configured kubeconfig and access to container registries for pulling images. The examples are intentionally small and won’t run standalone without cluster access. Combine them with your own cluster configuration, namespaces, and service definitions.

When you create a Deployment with Pulumi, it waits until the resource reaches a Ready state before completing. This means the Deployment controller has begun updating, a ReplicaSet exists matching the current revision, and the status shows an Available condition. For generation > 1, Pulumi also checks for a Progressing condition with NewReplicaSetAvailable reason. If readiness doesn’t occur within 10 minutes, the operation times out (configurable via customTimeouts).

Deploy replicated pods with label-based selection

Most Kubernetes workloads begin with a Deployment that manages multiple replicas of a pod, ensuring instances run continuously and recover from failures.

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

const deployment = new kubernetes.apps.v1.Deployment("deployment", {
    metadata: {
        labels: {
            app: "nginx",
        },
    },
    spec: {
        replicas: 3,
        selector: {
            matchLabels: {
                app: "nginx",
            },
        },
        template: {
            metadata: {
                labels: {
                    app: "nginx",
                },
            },
            spec: {
                containers: [{
                    image: "nginx:1.14.2",
                    name: "nginx",
                    ports: [{
                        containerPort: 80,
                    }],
                }],
            },
        },
    },
});
import pulumi
import pulumi_kubernetes as kubernetes

deployment = kubernetes.apps.v1.Deployment("deployment",
    metadata=kubernetes.meta.v1.ObjectMetaArgs(
        labels={
            "app": "nginx",
        },
    ),
    spec=kubernetes.apps.v1.DeploymentSpecArgs(
        replicas=3,
        selector=kubernetes.meta.v1.LabelSelectorArgs(
            match_labels={
                "app": "nginx",
            },
        ),
        template=kubernetes.core.v1.PodTemplateSpecArgs(
            metadata=kubernetes.meta.v1.ObjectMetaArgs(
                labels={
                    "app": "nginx",
                },
            ),
            spec=kubernetes.core.v1.PodSpecArgs(
                containers=[kubernetes.core.v1.ContainerArgs(
                    image="nginx:1.14.2",
                    name="nginx",
                    ports=[kubernetes.core.v1.ContainerPortArgs(
                        container_port=80,
                    )],
                )],
            ),
        ),
    ))
package main

import (
	appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
	corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
	metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appsv1.NewDeployment(ctx, "deployment", &appsv1.DeploymentArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Labels: pulumi.StringMap{
					"app": pulumi.String("nginx"),
				},
			},
			Spec: &appsv1.DeploymentSpecArgs{
				Replicas: pulumi.Int(3),
				Selector: &metav1.LabelSelectorArgs{
					MatchLabels: pulumi.StringMap{
						"app": pulumi.String("nginx"),
					},
				},
				Template: &corev1.PodTemplateSpecArgs{
					Metadata: &metav1.ObjectMetaArgs{
						Labels: pulumi.StringMap{
							"app": pulumi.String("nginx"),
						},
					},
					Spec: &corev1.PodSpecArgs{
						Containers: corev1.ContainerArray{
							&corev1.ContainerArgs{
								Image: pulumi.String("nginx:1.14.2"),
								Name:  pulumi.String("nginx"),
								Ports: corev1.ContainerPortArray{
									&corev1.ContainerPortArgs{
										ContainerPort: pulumi.Int(80),
									},
								},
							},
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Kubernetes = Pulumi.Kubernetes;

return await Deployment.RunAsync(() => 
{
    var deployment = new Kubernetes.Apps.V1.Deployment("deployment", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Labels = 
            {
                { "app", "nginx" },
            },
        },
        Spec = new Kubernetes.Types.Inputs.Apps.V1.DeploymentSpecArgs
        {
            Replicas = 3,
            Selector = new Kubernetes.Types.Inputs.Meta.V1.LabelSelectorArgs
            {
                MatchLabels = 
                {
                    { "app", "nginx" },
                },
            },
            Template = new Kubernetes.Types.Inputs.Core.V1.PodTemplateSpecArgs
            {
                Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
                {
                    Labels = 
                    {
                        { "app", "nginx" },
                    },
                },
                Spec = new Kubernetes.Types.Inputs.Core.V1.PodSpecArgs
                {
                    Containers = new[]
                    {
                        new Kubernetes.Types.Inputs.Core.V1.ContainerArgs
                        {
                            Image = "nginx:1.14.2",
                            Name = "nginx",
                            Ports = new[]
                            {
                                new Kubernetes.Types.Inputs.Core.V1.ContainerPortArgs
                                {
                                    ContainerPortValue = 80,
                                },
                            },
                        },
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.kubernetes.apps_v1.Deployment;
import com.pulumi.kubernetes.apps_v1.DeploymentArgs;
import com.pulumi.kubernetes.meta_v1.inputs.ObjectMetaArgs;
import com.pulumi.kubernetes.apps_v1.inputs.DeploymentSpecArgs;
import com.pulumi.kubernetes.meta_v1.inputs.LabelSelectorArgs;
import com.pulumi.kubernetes.core_v1.inputs.PodTemplateSpecArgs;
import com.pulumi.kubernetes.core_v1.inputs.PodSpecArgs;
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 deployment = new Deployment("deployment", DeploymentArgs.builder()        
            .metadata(ObjectMetaArgs.builder()
                .labels(Map.of("app", "nginx"))
                .build())
            .spec(DeploymentSpecArgs.builder()
                .replicas(3)
                .selector(LabelSelectorArgs.builder()
                    .matchLabels(Map.of("app", "nginx"))
                    .build())
                .template(PodTemplateSpecArgs.builder()
                    .metadata(ObjectMetaArgs.builder()
                        .labels(Map.of("app", "nginx"))
                        .build())
                    .spec(PodSpecArgs.builder()
                        .containers(ContainerArgs.builder()
                            .image("nginx:1.14.2")
                            .name("nginx")
                            .ports(ContainerPortArgs.builder()
                                .containerPort(80)
                                .build())
                            .build())
                        .build())
                    .build())
                .build())
            .build());

    }
}
description: Create a Deployment with auto-naming
name: yaml-example
resources:
    deployment:
        properties:
            metadata:
                labels:
                    app: nginx
            spec:
                replicas: 3
                selector:
                    matchLabels:
                        app: nginx
                template:
                    metadata:
                        labels:
                            app: nginx
                    spec:
                        containers:
                            - image: nginx:1.14.2
                              name: nginx
                              ports:
                                - containerPort: 80
        type: kubernetes:apps/v1:Deployment
runtime: yaml

The replicas property sets the desired pod count; the Deployment controller maintains this number continuously. The selector.matchLabels property tells the Deployment which pods it owns (matching the labels in template.metadata.labels). The template.spec.containers array defines the container image and exposed ports. Kubernetes continuously reconciles actual state toward this desired state.

Specify an explicit deployment name

When you need predictable resource names for external references or CI/CD pipelines, override Pulumi’s automatic naming with metadata.name.

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

const deployment = new kubernetes.apps.v1.Deployment("deployment", {
    metadata: {
        labels: {
            app: "nginx",
        },
        name: "nginx-deployment",
    },
    spec: {
        replicas: 3,
        selector: {
            matchLabels: {
                app: "nginx",
            },
        },
        template: {
            metadata: {
                labels: {
                    app: "nginx",
                },
            },
            spec: {
                containers: [{
                    image: "nginx:1.14.2",
                    name: "nginx",
                    ports: [{
                        containerPort: 80,
                    }],
                }],
            },
        },
    },
});
import pulumi
import pulumi_kubernetes as kubernetes

deployment = kubernetes.apps.v1.Deployment("deployment",
    metadata=kubernetes.meta.v1.ObjectMetaArgs(
        labels={
            "app": "nginx",
        },
        name="nginx-deployment",
    ),
    spec=kubernetes.apps.v1.DeploymentSpecArgs(
        replicas=3,
        selector=kubernetes.meta.v1.LabelSelectorArgs(
            match_labels={
                "app": "nginx",
            },
        ),
        template=kubernetes.core.v1.PodTemplateSpecArgs(
            metadata=kubernetes.meta.v1.ObjectMetaArgs(
                labels={
                    "app": "nginx",
                },
            ),
            spec=kubernetes.core.v1.PodSpecArgs(
                containers=[kubernetes.core.v1.ContainerArgs(
                    image="nginx:1.14.2",
                    name="nginx",
                    ports=[kubernetes.core.v1.ContainerPortArgs(
                        container_port=80,
                    )],
                )],
            ),
        ),
    ))
package main

import (
	appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/apps/v1"
	corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/core/v1"
	metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v4/go/kubernetes/meta/v1"
	"github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
	pulumi.Run(func(ctx *pulumi.Context) error {
		_, err := appsv1.NewDeployment(ctx, "deployment", &appsv1.DeploymentArgs{
			Metadata: &metav1.ObjectMetaArgs{
				Labels: pulumi.StringMap{
					"app": pulumi.String("nginx"),
				},
				Name: pulumi.String("nginx-deployment"),
			},
			Spec: &appsv1.DeploymentSpecArgs{
				Replicas: pulumi.Int(3),
				Selector: &metav1.LabelSelectorArgs{
					MatchLabels: pulumi.StringMap{
						"app": pulumi.String("nginx"),
					},
				},
				Template: &corev1.PodTemplateSpecArgs{
					Metadata: &metav1.ObjectMetaArgs{
						Labels: pulumi.StringMap{
							"app": pulumi.String("nginx"),
						},
					},
					Spec: &corev1.PodSpecArgs{
						Containers: corev1.ContainerArray{
							&corev1.ContainerArgs{
								Image: pulumi.String("nginx:1.14.2"),
								Name:  pulumi.String("nginx"),
								Ports: corev1.ContainerPortArray{
									&corev1.ContainerPortArgs{
										ContainerPort: pulumi.Int(80),
									},
								},
							},
						},
					},
				},
			},
		})
		if err != nil {
			return err
		}
		return nil
	})
}
using System.Collections.Generic;
using System.Linq;
using Pulumi;
using Kubernetes = Pulumi.Kubernetes;

return await Deployment.RunAsync(() => 
{
    var deployment = new Kubernetes.Apps.V1.Deployment("deployment", new()
    {
        Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
        {
            Labels = 
            {
                { "app", "nginx" },
            },
            Name = "nginx-deployment",
        },
        Spec = new Kubernetes.Types.Inputs.Apps.V1.DeploymentSpecArgs
        {
            Replicas = 3,
            Selector = new Kubernetes.Types.Inputs.Meta.V1.LabelSelectorArgs
            {
                MatchLabels = 
                {
                    { "app", "nginx" },
                },
            },
            Template = new Kubernetes.Types.Inputs.Core.V1.PodTemplateSpecArgs
            {
                Metadata = new Kubernetes.Types.Inputs.Meta.V1.ObjectMetaArgs
                {
                    Labels = 
                    {
                        { "app", "nginx" },
                    },
                },
                Spec = new Kubernetes.Types.Inputs.Core.V1.PodSpecArgs
                {
                    Containers = new[]
                    {
                        new Kubernetes.Types.Inputs.Core.V1.ContainerArgs
                        {
                            Image = "nginx:1.14.2",
                            Name = "nginx",
                            Ports = new[]
                            {
                                new Kubernetes.Types.Inputs.Core.V1.ContainerPortArgs
                                {
                                    ContainerPortValue = 80,
                                },
                            },
                        },
                    },
                },
            },
        },
    });

});
package generated_program;

import com.pulumi.Context;
import com.pulumi.Pulumi;
import com.pulumi.core.Output;
import com.pulumi.kubernetes.apps_v1.Deployment;
import com.pulumi.kubernetes.apps_v1.DeploymentArgs;
import com.pulumi.kubernetes.meta_v1.inputs.ObjectMetaArgs;
import com.pulumi.kubernetes.apps_v1.inputs.DeploymentSpecArgs;
import com.pulumi.kubernetes.meta_v1.inputs.LabelSelectorArgs;
import com.pulumi.kubernetes.core_v1.inputs.PodTemplateSpecArgs;
import com.pulumi.kubernetes.core_v1.inputs.PodSpecArgs;
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 deployment = new Deployment("deployment", DeploymentArgs.builder()        
            .metadata(ObjectMetaArgs.builder()
                .labels(Map.of("app", "nginx"))
                .name("nginx-deployment")
                .build())
            .spec(DeploymentSpecArgs.builder()
                .replicas(3)
                .selector(LabelSelectorArgs.builder()
                    .matchLabels(Map.of("app", "nginx"))
                    .build())
                .template(PodTemplateSpecArgs.builder()
                    .metadata(ObjectMetaArgs.builder()
                        .labels(Map.of("app", "nginx"))
                        .build())
                    .spec(PodSpecArgs.builder()
                        .containers(ContainerArgs.builder()
                            .image("nginx:1.14.2")
                            .name("nginx")
                            .ports(ContainerPortArgs.builder()
                                .containerPort(80)
                                .build())
                            .build())
                        .build())
                    .build())
                .build())
            .build());

    }
}
description: Create a Deployment with a user-specified name
name: yaml-example
resources:
    deployment:
        properties:
            metadata:
                labels:
                    app: nginx
                name: nginx-deployment
            spec:
                replicas: 3
                selector:
                    matchLabels:
                        app: nginx
                template:
                    metadata:
                        labels:
                            app: nginx
                    spec:
                        containers:
                            - image: nginx:1.14.2
                              name: nginx
                              ports:
                                - containerPort: 80
        type: kubernetes:apps/v1:Deployment
runtime: yaml

By default, Pulumi generates unique names for Kubernetes resources. Setting metadata.name to “nginx-deployment” creates a Deployment with that exact name in the cluster, making it easier to reference from kubectl, monitoring tools, or other automation. The rest of the configuration (replicas, selector, template) works identically to auto-named deployments.

Beyond These Examples

These snippets focus on specific Deployment-level features: replica management and pod templates, label-based pod selection, and automatic vs explicit resource naming. They’re intentionally minimal rather than full application deployments.

The examples assume pre-existing infrastructure such as a Kubernetes cluster with configured kubeconfig and container registry access for pulling images. They focus on configuring the Deployment rather than provisioning cluster infrastructure or defining Services.

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

  • Resource limits and requests (resources block)
  • Health checks (livenessProbe, readinessProbe)
  • Rolling update strategy (strategy.type, maxSurge, maxUnavailable)
  • Environment variables and ConfigMap/Secret mounts
  • Namespace and annotation configuration

These omissions are intentional: the goal is to illustrate how Deployment replication and selection are wired, not provide drop-in application modules. See the Deployment resource reference for all available configuration options.

Frequently Asked Questions

Readiness & Timeouts
What's the default timeout for a Deployment to become ready, and how can I change it?
Deployments have a 10-minute default timeout. If the Deployment doesn’t reach a Ready state within this time, it times out and marks the resource update as Failed. Override this by setting the customTimeouts option on the resource.
What conditions determine when a Deployment is ready?
Four conditions must be met: (1) The Deployment controller has begun updating it, (2) A ReplicaSet exists matching the current revision, (3) The status.conditions field has an ‘Available’ condition with status ‘True’, (4) For generation > 1, status.conditions has a ‘Progressing’ condition with status ‘True’ and reason ‘NewReplicaSetAvailable’.
Why does my Deployment show different readiness checks for the first creation versus updates?
For generation 1 (initial creation), the ‘Progressing’ status check doesn’t apply because there’s no rollout, just a simple creation. For generation > 1 (updates), the ‘Progressing’ status with reason ‘NewReplicaSetAvailable’ is required to confirm a successful rollout.
Why is my Deployment timing out even though pods are running?
The Deployment waits for specific status conditions, not just pod readiness. Verify that status.conditions shows ‘Available’ as ‘True’, and for updates (generation > 1), check that ‘Progressing’ is ‘True’ with reason ‘NewReplicaSetAvailable’.
Label Selectors & Pod Templates
Do the selector labels need to match the pod template labels?
Yes, spec.selector.matchLabels must match spec.template.metadata.labels for the Deployment to correctly manage its pods. All examples show identical labels in both locations.
What happens if my selector doesn't match the pod template labels?
The Deployment won’t be able to select and manage its pods correctly, leading to failed rollouts or orphaned pods. Ensure the labels are identical in both selector.matchLabels and template.metadata.labels.
Naming & Configuration
Can I let Pulumi auto-generate my Deployment name?
Yes, omit metadata.name for auto-generated names. To use a specific name, set metadata.name explicitly (e.g., ’nginx-deployment’).
What's the recommended number of replicas for a Deployment?
The examples show replicas: 3, but the appropriate number depends on your application’s availability and load requirements. Set spec.replicas to your desired count.

Ready to get started?

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

Create free account