Creating Kubernetes Deployments

By Vishvesh Patel · DevOps EngineerUpdated September 12, 2026

Write, apply, inspect and scale a Deployment: the required fields, the immutable selector, how ReplicaSets relate to it, and the errors you meet first.

A Deployment declares how many identical Pods should exist and what they should look like. A controller then makes that true and keeps it true.

The quickest version

kubectl create deployment web --image=nginx:1.27 --replicas=3
kubectl get deployments
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    3/3     3            3           8s

That is fine for experimenting and wrong for anything you intend to keep. Nothing records why the cluster is in this state, and nobody can reproduce it.

Use the imperative command to generate a manifest instead:

kubectl create deployment web --image=nginx:1.27 --replicas=3 \
  --dry-run=client -o yaml > deployment.yaml

Nothing is sent to the cluster; you get a valid skeleton to edit and commit.

The manifest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: payments-api
  labels:
    app: payments-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: payments-api          # must match template labels below
  template:
    metadata:
      labels:
        app: payments-api        # must match selector above
    spec:
      containers:
        - name: api
          image: ghcr.io/acme/payments-api:1.4.2
          ports:
            - containerPort: 8080
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              memory: 512Mi
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 5
kubectl apply -f deployment.yaml

Four things in that file deserve a moment.

selector.matchLabels must equal template.metadata.labels. The API server rejects a mismatch, which is helpful. The selector is how the Deployment identifies the Pods it owns.

The selector is immutable. Changing it after creation requires deleting and recreating the Deployment, so pick labels you can live with. app.kubernetes.io/name and app.kubernetes.io/instance are the recommended conventions if you want something more structured than app.

Pin the image tag. :1.4.2, never :latest. A mutable tag means two Pods from one manifest can run different code, and a rollback has nothing to roll back to.

Set resource requests. Without them the scheduler assumes the Pod needs nothing, places it badly, and marks it BestEffort so it is evicted first under pressure.

What it actually created

kubectl get deployment,replicaset,pods -l app=payments-api
NAME                           READY   UP-TO-DATE   AVAILABLE
deployment.apps/payments-api   3/3     3            3

NAME                                      DESIRED   CURRENT   READY
replicaset.apps/payments-api-7d4f9c8b5    3         3         3

NAME                                READY   STATUS    RESTARTS   AGE
pod/payments-api-7d4f9c8b5-2xk9p    1/1     Running   0          22s
pod/payments-api-7d4f9c8b5-7mq4t    1/1     Running   0          22s
pod/payments-api-7d4f9c8b5-k3n8w    1/1     Running   0          22s

Three objects, one command. The Deployment created a ReplicaSet, the ReplicaSet created the Pods, and the Pod names carry both the ReplicaSet hash and a random suffix.

That hash is derived from the pod template. Change the template and you get a new ReplicaSet with a new hash, which is the mechanism behind rolling updates and rollbacks.

The columns are worth reading precisely:

ColumnMeaning
READYPods passing readiness / desired
UP-TO-DATEPods running the current template
AVAILABLEReady for at least minReadySeconds

A Deployment showing 3/3 ready but 1 up-to-date is mid-rollout.

Scaling

kubectl scale deployment/payments-api --replicas=5

This changes the ReplicaSet in place, scaling does not create a new revision, because the pod template has not changed.

If you scale imperatively, remember the manifest still says 3, so the next kubectl apply reverts it. Either change the file or, if load varies, use a Horizontal Pod Autoscaler and remove replicas from the manifest altogether so the two do not fight.

Inspecting

kubectl describe deployment payments-api     # spec, conditions, events
kubectl get deployment payments-api -o yaml  # full object as stored
kubectl rollout status deployment/payments-api

kubectl describe shows conditions that answer most questions directly:

Conditions:
  Type           Status  Reason
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable

Available: False means not enough Pods are ready. Progressing: False with reason ProgressDeadlineExceeded means a rollout gave up.

The errors you will hit

selector does not match template labels: the two label sets disagree. Make them identical.

field is immutable on spec.selector. You changed the selector on an existing Deployment. Delete and recreate it.

Pods Pending: nothing can schedule them. kubectl describe pod names the reason: insufficient CPU or memory, an untolerated taint, or an unbound volume claim.

Pods ImagePullBackOff: wrong image name or tag, or a private registry with no imagePullSecrets in this namespace.

0/3 ready and staying that way: the containers start but fail readiness. Check the probe path and port, and give slow-starting applications a startup probe rather than a longer initialDelaySeconds.

Deleting

kubectl delete deployment payments-api
# or, preferably
kubectl delete -f deployment.yaml

Deleting a Deployment cascades to its ReplicaSets and Pods through owner references. Deleting the ReplicaSet directly is a mistake. The Deployment simply recreates it.

Quick reference

TaskCommand
Generate a manifestkubectl create deployment <n> --image=<i> --dry-run=client -o yaml
Applykubectl apply -f deployment.yaml
Statuskubectl get deployments
Diagnosekubectl describe deployment <n>
See the ReplicaSetskubectl get rs -l app=<label>
Scalekubectl scale deployment/<n> --replicas=5
Watch a rolloutkubectl rollout status deployment/<n>
Restart all Podskubectl rollout restart deployment/<n>
Deletekubectl delete -f deployment.yaml

Key takeaways

  • Generate manifests with --dry-run=client -o yaml, then commit them. Do not manage real workloads imperatively.
  • selector.matchLabels must equal template.metadata.labels, and the selector is immutable afterwards.
  • A Deployment creates a ReplicaSet, which creates the Pods. The hash in Pod names comes from the template.
  • Scaling changes the ReplicaSet in place and creates no new revision.
  • Pin image tags and set resource requests. Both are one line and both prevent a class of incident.
  • READY, UP-TO-DATE and AVAILABLE mean different things: read all three during a rollout.
  • Delete the Deployment, never the ReplicaSet.

Check your understanding

Exercise

Changing the selector

You edit an existing Deployment to change selector.matchLabels from app: api to app.kubernetes.io/name: api, and update the template labels to match. kubectl apply fails. Why?

Exercise

A scale that did not stick

You run kubectl scale deployment/api --replicas=10 during a traffic spike. The next CI deploy runs kubectl apply -f deployment.yaml and the count drops to 3. Why?


Frequently Asked Questions

How do I create a Kubernetes Deployment?

Write a manifest with apiVersion: apps/v1, kind: Deployment, a replica count, a selector, and a pod template, then run kubectl apply -f deployment.yaml. The fastest way to get a correct starting point is kubectl create deployment web --image=nginx:1.27 --dry-run=client -o yaml > deployment.yaml, which generates valid YAML without contacting the cluster. Edit that, commit it, and apply it. Managing real workloads through imperative commands leaves no record of why the cluster looks the way it does.

Why does my Deployment fail with "selector does not match template labels"?

spec.selector.matchLabels and spec.template.metadata.labels must contain the same key-value pairs. The selector is how the Deployment recognises the Pods it owns, so a mismatch would mean it creates Pods it cannot then manage, and the API server rejects it. Make the two blocks identical. Note that the selector is immutable after creation, so if you need a different label scheme later you must delete and recreate the Deployment.

What is the difference between a Deployment and a ReplicaSet?

A ReplicaSet keeps a fixed number of identical Pods running and does nothing else. A Deployment manages ReplicaSets and adds version handling: changing the pod template creates a new ReplicaSet, shifts replicas from the old one to the new gradually, and retains previous ReplicaSets at zero replicas so you can roll back. You create Deployments; the ReplicaSets are an implementation detail you mostly read rather than write.

Why are my Deployment's Pods stuck at 0/3 ready?

The containers are running but failing their readiness probe, so they never become Ready. Run kubectl describe pod <name> and look at the events for the probe's failure message. The usual causes are a wrong path or port, an endpoint behind authentication returning 401, or an initialDelaySeconds shorter than the application's genuine startup time. For slow-starting applications, add a startup probe rather than inflating initialDelaySeconds, which would otherwise delay detection of real failures later.

How do I scale a Deployment?

kubectl scale deployment/<name> --replicas=5 changes it immediately, and editing replicas in the manifest and reapplying does the same declaratively. Remember that imperative scaling is undone by the next kubectl apply if the file still specifies a different number. Where load varies, a HorizontalPodAutoscaler is the better answer, and when you adopt one, remove replicas from the manifest entirely so the autoscaler and your deployment tooling are not writing the same field.

What do READY, UP-TO-DATE and AVAILABLE mean?

READY shows how many Pods are passing readiness checks against how many are desired. UP-TO-DATE counts Pods running the current pod template, which is how you see rollout progress. AVAILABLE counts Pods that have been ready for at least minReadySeconds, so it lags READY slightly when that is configured. During a rollout these diverge, 3/3 ready with only 1 up-to-date means the new version is still being rolled out.

Continue Learning

Explore Related Topics

Try the Tool

Practise this in the browser

Kubernetes Playground

Practise kubectl against a simulated cluster in your browser.

When it breaks

Related Resources