Blog · April 29, 2025 · Updated on September 7, 2026 · 13 min read

Kubernetes Deployment: Rollouts Explained

A factory worker stands next to a conveyor belt transporting gray pipe fittings
Photo: Galib Rahman Nadim / Pexels

A Kubernetes Deployment describes which Pods should run and how many of them, and makes sure the cluster reaches exactly that state and keeps it. If a Pod dies, a new one appears. If you change the container image, the Deployment swaps the Pods out one step at a time without taking your application down. And if the new version doesn't work, a single command brings you back to the previous one.

That makes the Deployment the object you use to run almost every stateless application in Kubernetes: web servers, APIs, workers. If you only understand Pods, you haven't really used Kubernetes yet. If you understand the Deployment, you understand the core.

I run Kubernetes clusters in production, roll out new versions through them every week and gave the topic its own section in the third chapter of my Kubernetes book with Rheinwerk. This is the short version, including the pitfalls I know from my own mistakes. Everything I've written about Kubernetes is collected on the Kubernetes page.

Pod, ReplicaSet, Deployment: who manages whom

A single Pod in Kubernetes has one problem: it is mortal. If the node goes down or the container crashes for good, the Pod is gone and nobody recreates it. For an application that is supposed to run permanently, you need an object that sits above the Pods and watches over them.

In Kubernetes, that job belongs to the ReplicaSet. It knows a Pod template, a desired count and a label selector. Every few seconds it compares how many Pods with the matching label are actually running against the number it is supposed to have. If one is missing, it creates a new one from the template. If there is one too many, it terminates it. A ReplicaSet can't do more than that, and it doesn't need to.

The Deployment sits one level above. It creates and owns the ReplicaSet and adds the logic the ReplicaSet lacks: rolling out versions, watching the rollout, pausing it and rolling back. Whenever the Pod template changes, the Deployment creates a new ReplicaSet, scales it up and scales the old one down in parallel. The old ReplicaSets stay behind, empty, and serve as your rollback memory.

Pod ReplicaSet Deployment
Runs an application? yes, exactly one instance yes, in the desired count yes, in the desired count
Replaces failed instances no yes yes, via the ReplicaSet
Rolls out new versions in a controlled way no no yes
Rollback to an older version no no yes
Created directly only for experiments practically never yes, as the default

Technically, the three are tied together through owner references. Every Pod carries a reference in its metadata to the ReplicaSet that created it, and the ReplicaSet carries one to the Deployment. Delete the Deployment and Kubernetes cleans up the ReplicaSet and the Pods along with it. The Kubernetes documentation describes this under Owners and Dependents.

This ownership chain leads to a rule that trips up many beginners: editing the ReplicaSet directly is pointless. If you raise the replica count there, the Deployment resets it to its own value within seconds. The Deployment is the single source of truth; everything below it is derived.

Kubernetes Deployment YAML: the manifest line by line

A Deployment manifest looks confusingly similar to a Pod manifest, because the Pod template is embedded in it. What's new are the fields replicas, selector and strategy. The following example is complete and runs in any cluster:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-api
  labels:
    app: shop-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: shop-api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1
  template:
    metadata:
      labels:
        app: shop-api
    spec:
      containers:
        - name: api
          image: nginx:stable
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 3
            periodSeconds: 5

replicas defines how many Pods should run at the same time. selector.matchLabels tells the ReplicaSet which Pods belong to it and has to match the labels under template.metadata.labels exactly, otherwise the API server rejects the manifest. Everything under template is the Pod specification you already know. strategy controls how an update proceeds, more on that in a moment. The readiness probe isn't mandatory, but without it a rollout without downtime is a matter of luck, which is why it goes into every Deployment I write.

Apply the manifest with kubectl apply -f deployment.yaml and shortly afterwards you'll see all three levels:

$ kubectl get deployment,replicaset,pods -l app=shop-api
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/shop-api   3/3     3            3           40s

NAME                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/shop-api-7d4b9c6f5    3         3         3       40s

NAME                            READY   STATUS    RESTARTS   AGE
pod/shop-api-7d4b9c6f5-2xk8p    1/1     Running   0          40s
pod/shop-api-7d4b9c6f5-9tqzn    1/1     Running   0          40s
pod/shop-api-7d4b9c6f5-m5vwd    1/1     Running   0          40s

The suffix 7d4b9c6f5 is the hash of the Pod template. Every ReplicaSet gets its own, and later on it tells you at a glance which Pod belongs to which version.

One note on the selector: once created, it is immutable. If you want to rename your Pod labels later, you have to delete the Deployment and create it again. So keep the selector minimal; a single app label is usually enough.

Scaling: change replicas instead of creating Pods

You never get more instances by creating Pods, only by changing the number in the Deployment. The quick way is kubectl scale deployment/shop-api --replicas=5. The ReplicaSet notices the difference and starts two additional Pods from the template. Scale back down and it terminates the surplus.

In production, though, the quick way is rarely the right one. In my clusters every Deployment lives as a manifest in Git and is rolled out through GitOps. If I change the replicas with kubectl scale, the cluster drifts away from the repository, and on the next sync the old value is back. If you work declaratively, you change the number in the manifest and let the pipeline roll it out. For automatic scaling based on load there is the Horizontal Pod Autoscaler, which adjusts exactly this field for you.

Rolling update: understanding maxSurge and maxUnavailable

Change anything in the Pod template, usually the image, and the Deployment starts a rollout. How it proceeds is controlled by the strategy field, also called the Deployment strategy in the documentation. Kubernetes knows two variants:

Recreate RollingUpdate
Procedure delete all old Pods, then start all new ones replace old and new Pods with overlap
Downtime yes, always none, if the application cooperates
Two versions active at once never yes, briefly
Suitable for development clusters, applications that may only run once anything stateless and horizontally scalable
Default no yes

During a rolling update the Deployment creates a second ReplicaSet with the new template and moves the Pods over piece by piece: new ReplicaSet up by one, old one down by one, wait until the new Pod is ready, repeat. Two values determine how big those steps are. maxUnavailable says how many Pods may be missing below the desired count during the update. maxSurge says how many Pods may run above the desired count in addition. Both accept a fixed number or a percentage, and the default for each is 25 percent. With percentages, Kubernetes rounds maxUnavailable down and maxSurge up.

With the values from the manifest above, three replicas and 1 for each, that means: never fewer than two Pods available, never more than four present at the same time. The rollout then runs in steps: a new Pod starts, and as soon as it is ready an old one is terminated. Then the next. After three such steps only Pods of the new version are left, and at no point were fewer than two of them answering requests.

Two settings from practice: maxUnavailable: 0 with maxSurge: 1 is the conservative option for applications where every instance matters, but it costs room for one extra Pod. On tight nodes where no additional Pod fits, you go the other way, maxSurge: 0 with maxUnavailable: 1, and the application briefly runs with one instance fewer during the update.

No readiness probe, no zero-downtime rollout

The rolling update is only as good as the answer to one question: when is a new Pod done? Without a readiness probe, a Pod counts as ready as soon as its containers have started. For an application that spends ten seconds loading configuration or opening a database connection after start, that is too early. Kubernetes then terminates the next old Pod while the new one can't answer requests yet, and your users see errors.

With a readiness probe, the Deployment waits until the application itself reports that it is ready. Only then does the rollout move on. The second benefit matters even more: if the new version doesn't start at all, the new Pod never becomes ready, the Deployment never gets past the first step, and the old Pods keep running. A broken release never reaches the whole cluster this way. And that exact case brings us to the stuck rollout.

Watching a rollout, spotting a stuck one, triggering a rollback

After every update I watch with kubectl rollout status. For a healthy rollout the command finishes within seconds with a success message. With a broken image it looks like this:

$ kubectl rollout status deployment/shop-api
Waiting for deployment "shop-api" rollout to finish: 1 out of 3 new replicas have been updated...
Waiting for deployment "shop-api" rollout to finish: 1 out of 3 new replicas have been updated...
error: deployment "shop-api" exceeded its progress deadline

$ kubectl get pods -l app=shop-api
NAME                         READY   STATUS             RESTARTS   AGE
shop-api-7d4b9c6f5-9tqzn     1/1     Running            0          14m
shop-api-7d4b9c6f5-m5vwd     1/1     Running            0          14m
shop-api-98f6d7c4b-x2lrq     0/1     CrashLoopBackOff   6          10m

The output tells the whole story. Two old Pods keep running, the one new Pod crashes on start and never becomes ready, so the Deployment doesn't move on. The error after ten minutes comes from progressDeadlineSeconds, 600 seconds by default. After that period the Deployment marks the rollout as failed but does nothing further on its own. It doesn't roll back, it waits.

What exactly is wrong with the new Pod is revealed by kubectl describe pod in the events and kubectl logs for the container. Common causes in my projects: an image tag that doesn't exist in the registry, a missing Secret that is expected as an environment variable, or a readiness probe pointing at a path the new version renamed.

For the way back there is the Deployment's revision list and the undo command:

$ kubectl rollout history deployment/shop-api
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

$ kubectl rollout undo deployment/shop-api
deployment.apps/shop-api rolled back

kubectl rollout undo scales the previous ReplicaSet back up and the broken one down, following the same rules as a normal rolling update. With --to-revision you jump to a specific older revision. How many of them Kubernetes keeps is controlled by revisionHistoryLimit, ten by default. Set it to zero and there is no rollback anymore.

One piece of advice from operations: the kubectl rollback is the emergency brake, not the process. After a rollout undo your cluster no longer matches your Git repository, and the next person who syncs rolls the broken release out again. In my projects a rollback therefore happens as a revert in the repository, which the pipeline rolls out. The details, including pause and resume during a rollout, are in Rolling Update and Rollback in Kubernetes.

Kubernetes Deployment vs. StatefulSet vs. DaemonSet

The Deployment is the right controller for stateless applications where any Pod can replace any other. As soon as Pods need a fixed identity or their own storage, or have to run exactly once on every node, a different object fits better.

Deployment StatefulSet DaemonSet
Pod names random hash fixed and numbered (db-0, db-1) one per node
Own storage per Pod no, all share yes, one volume per Pod rarely
Order on start and update arbitrary, parallel ordered, one after the other per node
Count determined by replicas replicas number of nodes
Typical application web server, API, worker database, message broker, cache log agent, monitoring, network plugin

The most common wrong call I see is a database in a Deployment with a volume attached. As long as only one replica runs, it works. On the first rolling update, Kubernetes starts the second Pod before the first one is gone, and both want the same volume. For anything that holds state, the StatefulSet in Kubernetes was built.

Frequently asked questions

What is the difference between a Deployment and a ReplicaSet?

The ReplicaSet keeps a fixed number of identical Pods alive, nothing more. The Deployment manages ReplicaSets and adds rollouts, rollback and pause on top. For every new version it creates a new ReplicaSet. In daily work you only create Deployments; ReplicaSets appear automatically.

Rolling update or Recreate, which should I use?

RollingUpdate, as long as your application can run several instances in parallel and holds no local state. Recreate only when two versions running at the same time would cause damage, for example a database migration that isn't backwards compatible, or in a test cluster with no room for an extra Pod.

Why is my rollout stuck?

Almost always because the new Pod never becomes ready: wrong image tag, missing Secret or ConfigMap, crash on start, or a readiness probe that never succeeds. Check the status of the new Pod with kubectl get pods, then the events with kubectl describe pod and the application with kubectl logs. The Deployment waits until you fix the cause or roll back.

When do I need a StatefulSet instead of a Deployment?

When every Pod needs its own persistent storage, must have a stable name or should start in a fixed order. That applies to databases, message brokers and distributed caches. For web servers, APIs and workers without local state, the Deployment is the better and simpler choice.

How do I roll back to the previous version?

kubectl rollout undo deployment/<name> takes you one revision back, --to-revision=<n> to a specific one. Kubernetes then scales the old ReplicaSet back up. Afterwards you should reset your manifest in the repository as well, otherwise the next sync rolls the broken version out again.

Where to go next

Once you understand the Deployment, you know the mechanism Kubernetes uses to roll out practically every application. What goes into the Pod template is explained in Kubernetes Pod: what is a Pod?. How to pause, verify and cleanly revert rollouts in production is covered in Rolling Update and Rollback in Kubernetes. And when you need a StatefulSet instead of a Deployment is shown in Kubernetes StatefulSet vs Deployment.

My suggestion for today: take the manifest above, apply it, change the image to a tag that doesn't exist and watch with kubectl rollout status and kubectl get pods how the Deployment stops while the old Pods keep running. Then rollout undo. Once you've seen that happen, you trust the mechanism. The complete reference for all fields is in the Kubernetes Deployment documentation.

In full detail with all examples, from the ReplicaSet experiment to diffing two revisions, this is covered in chapter 3 of my book "Kubernetes: Practical Guide for Developers and DevOps Teams" (Rheinwerk Computing). You can find all the information about the book on my Kubernetes page.

Kevin Welter

Kevin Welter

Developer, IT architect, author of technical books (Kubernetes, cloud infrastructures) and speaker. Runs his business with an AI workforce of eight AI employees and shows solo business owners in his community how to hire their first AI employee.

More about AI employees

Kubernetes from the basics to a production-ready cluster

Get the book