A Kubernetes rolling update replaces the Pods of a Deployment one after another with a new version, instead of replacing all of them at once. That keeps your application reachable throughout the update, as long as enough old Pods keep running until the new ones are ready. If something goes wrong along the way, a rollback undoes the change and brings the previous version back.
For developers and DevOps teams, this is the process that decides how every release goes: run it cleanly and nobody notices a thing. Let it get stuck, because a configuration is missing or a health check never turns green, and in the worst case your application ends up half updated and half old at the same time.
I run Kubernetes clusters in production and wrote the Kubernetes Practical Guide published by Rheinwerk Computing. The levers and failure patterns in this article come from real rollouts, not just from theory. You can find all my Kubernetes articles collected on the Kubernetes page.
Deployment and ReplicaSet: who actually controls the rollout
A rolling update never runs directly against individual Pods, but through two objects working together. The Deployment knows the desired number of Pods, the update strategy and the history of earlier versions. The ReplicaSet underneath is the object that does the work: it only makes sure that exactly the number of Pods currently assigned to it are running.
When you update a Deployment, for example with a new image, Kubernetes creates a completely new ReplicaSet for the new version in the background. The old ReplicaSet stays in place but is scaled down to zero Pods step by step while the new one scales up. These two ReplicaSets sitting side by side are also why a rollback is so easy: the old Pod specification has not gone anywhere, it is just sitting at zero replicas for the moment.
How many ReplicaSets Kubernetes keeps around for you is controlled by revisionHistoryLimit on the Deployment. The default is enough for most cases, but never set it to zero, or you will have nothing left to roll back to with kubectl.
Kubernetes rolling update strategy: maxSurge and maxUnavailable
Kubernetes knows two strategies for an update. Recreate deletes all running Pods first and only then starts the new ones, which guarantees downtime and is therefore only an option for development environments with tight resources. RollingUpdate is the default and replaces Pods step by step, so your application stays reachable throughout the whole update.
Two values control how cautious or aggressive this replacement is:
| Option |
Meaning |
Default |
| maxUnavailable |
how many Pods can be missing at the same time during the update, at most |
25% |
| maxSurge |
how many extra Pods beyond the desired count are briefly allowed |
25% |
You can give both values as a fixed number or as a percentage, and Kubernetes rounds percentages differently: maxUnavailable down, maxSurge up. A Deployment with three replicas and the default values therefore ends up at maxUnavailable 0 and maxSurge 1. No Pod may be missing during the update, one extra Pod is briefly allowed, so up to four in total. Since that outcome shifts every time the replica count changes, for applications where every Pod that drops out is noticeable I set maxUnavailable to 0 explicitly and let Kubernetes scale up extra capacity through maxSurge instead, before an old Pod disappears.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-backend
spec:
replicas: 4
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 0
maxSurge: 1
selector:
matchLabels:
app: web-backend
template:
metadata:
labels:
app: web-backend
spec:
containers:
- name: web-backend
image: registry.example.com/web-backend:c3f1a9d
ports:
- containerPort: 8080
Once you have rolled it out, a single command tracks the progress:
kubectl rollout status deployment/web-backend
The command blocks until the update finishes and tells you immediately if a rollout gets stuck, instead of you running kubectl get pods by hand over and over.
No zero-downtime update without a readiness probe
Kubernetes replaces Pods even without any configuration, but without a readiness probe it has no way of knowing when a new Pod is actually ready to serve requests. Without that information, Kubernetes marks a Pod as available as soon as its container has started, regardless of whether the application inside it is already responding. Right in that window, requests then land on a Pod that cannot answer yet.
A readiness probe closes that gap: only once it succeeds does the matching Service add the Pod to its load balancing, and only then does the Deployment count the new Pod as successfully rolled out. If the probe fails, the rollout waits instead of wrongly moving on, and you see that immediately in kubectl rollout status. How to configure readiness, liveness and startup probes in detail is covered in Liveness and Readiness Probes Explained.
A rolling update without a readiness probe works technically, but without the guarantee you actually want. That is why I set at least one readiness probe on every production application, usually a simple HTTP endpoint that only returns 200 once the database connection and other dependencies are in place.
Rollback in practice: rollout history and rollout undo
Two commands are enough to get an overview of earlier versions and go back to one of them:
kubectl rollout history deployment/web-backend
kubectl rollout undo deployment/web-backend --to-revision=3
Without specifying a revision, rollout undo simply steps back to the previous version. Under the hood, Kubernetes runs technically the same process as for a normal update: it scales the old ReplicaSet back up and scales the current one down, including the same readiness checks and the same strategy parameters. A rollback is therefore not a special case, it is a rolling update running in the other direction.
By default, the history in rollout history only shows revision numbers without context, which makes rolling back to the right version harder once several changes have piled up. With kubectl rollout history deployment/web-backend --revision=3 you can display the complete Pod template of that revision and see exactly what changed.
Rollback through Git instead of kubectl rollout undo
kubectl rollout undo is quick to reach for during an active incident, but in my own clusters I hardly use it. The reason: after a rollback through kubectl, the state in the cluster drifts away from what your Git repository currently claims is current. Other developers still see the new version in the repository while the old one is already running again in the cluster, and that gap often only surfaces uncomfortably at the next deployment.
There is one more boundary to keep in mind: a rollback through old ReplicaSets only brings back the state of the Deployment, meaning the image, environment and configuration of the Pods. Whatever your application changed outside of that in the meantime does not come along. A database migration that renamed columns, a file written to disk, an order already sent: each needs its own way back. That is why I build migrations so the previous application version still runs against the new schema.
If your cluster runs on GitOps, for example with ArgoCD, the cleaner path is a rollback in the repository itself: revert the faulty commit or reset to the tag of the previous version and let the GitOps controller carry out the rollout. That way Git stays the single source of truth throughout, the history shows exactly when and why you rolled back, and the next regular sync does not accidentally overwrite your rollback again. I describe what this process looks like in detail in GitOps with ArgoCD: Deploy from Git; the ArgoCD documentation explains the sync and rollback mechanics in full.
kubectl rollout undo still makes sense when nobody has time to build a commit first and the application needs to be running again right away. Just make sure you catch up the Git state afterward, so the repository and the cluster match again.
Frequently asked questions
What is the difference between a rolling update and Recreate?
RollingUpdate replaces Pods step by step and keeps the application reachable while doing it, Recreate deletes all Pods first and then starts the new ones, which causes downtime. RollingUpdate is the default and, for production applications, almost always the right choice.
How do I do a rollback in Kubernetes?
With kubectl rollout undo deployment/<name> you go back to the previous version, and with the addition --to-revision=<number> to a specific earlier version. If your cluster runs on GitOps, a revert in the Git repository is the cleaner path, because it keeps the cluster and the repository consistent.
What do maxSurge and maxUnavailable mean?
maxUnavailable limits how many Pods can be missing at the same time during an update, maxSurge limits how many extra Pods beyond the desired count are briefly allowed. Both can be given as a fixed number or as a percentage and default to 25 percent, with Kubernetes rounding maxUnavailable down and maxSurge up.
Why does my rollout get stuck in the middle of an update?
Most often because a new Pod is not reporting a successful readiness probe, for example due to a configuration error or an unreachable dependency. Kubernetes then waits instead of wrongly reporting the rollout as successful. Running kubectl describe pod on one of the new Pods usually reveals the reason right away.
Why do you use Git instead of kubectl rollout undo for a rollback?
Because a rollback through kubectl decouples the cluster from the state in the Git repository: the cluster then runs an old version while the repository still shows the new one. A revert in the repository keeps both states in sync and documents the rollback traceably in the commit history.
Where to go next
If you want to go through the fundamentals of the Deployment object again from the start, you will find them in Kubernetes Deployment: Rollouts Explained. For the readiness, liveness and startup probes, without which no rolling update is really safe, it is worth reading Liveness and Readiness Probes Explained. And if you want to control rollbacks through Git instead of kubectl going forward, you will find the starting point in GitOps with ArgoCD: Deploy from Git.
My suggestion for today: take an existing Deployment, set maxUnavailable to 0 as a test and watch with kubectl rollout status and kubectl get events how carefully Kubernetes proceeds as a result. In full detail with all examples, from the ReplicaSet fundamentals through to the probe parameters, this is covered in chapters 3 and 8 of my Kubernetes Practical Guide (Rheinwerk Computing).