Rolling updates are the default way most people ship changes to Kubernetes, and honestly, they’re the strategy I reach for most often myself — they’re built into the Deployment object, require no extra tooling, and gradually replace old Pods with new ones without any downtime, as long as you configure them correctly. In this guide I’ll explain exactly how the rolling update algorithm works, walk through triggering and monitoring one, and cover the configuration knobs that determine whether your rollout is smooth or chaotic.
How Rolling Updates Work
A Deployment doesn’t directly manage Pods — it manages ReplicaSets, and each ReplicaSet manages Pods. When you update a Deployment’s Pod template (say, a new container image), Kubernetes creates a new ReplicaSet with the updated spec, and gradually scales it up while scaling the old ReplicaSet down, governed by two settings: maxSurge and maxUnavailable.
- maxSurge — how many Pods above the desired replica count can exist during the rollout.
- maxUnavailable — how many Pods below the desired replica count are tolerated during the rollout.
The default for both is 25%, which for most workloads produces a smooth, gradual replacement.
Step 1: A Baseline Deployment
# myapp-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
spec:
replicas: 6
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
selector:
matchLabels:
app: myapp
template:
metadata:
labels:
app: myapp
spec:
containers:
- name: myapp
image: myrepo/myapp:1.0.0
ports:
- containerPort: 8080
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
resources:
requests:
cpu: 100m
memory: 128Mi
kubectl apply -f myapp-deployment.yaml
kubectl rollout status deployment/myapp
Waiting for deployment "myapp" rollout to finish: 6 of 6 updated replicas are available...
deployment "myapp" successfully rolled out
Step 2: Trigger a Rolling Update
The simplest way to trigger one is updating the image:
kubectl set image deployment/myapp myapp=myrepo/myapp:1.1.0
Or edit the manifest and reapply:
kubectl apply -f myapp-deployment.yaml
Watch it happen in real time:
kubectl rollout status deployment/myapp
Waiting for deployment "myapp" rollout to finish: 2 out of 6 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 3 out of 6 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 4 out of 6 new replicas have been updated...
Waiting for deployment "myapp" rollout to finish: 5 old replicas are pending termination...
deployment "myapp" successfully rolled out
You can also watch the actual Pods being replaced:
kubectl get pods -l app=myapp --watch
NAME READY STATUS RESTARTS AGE
myapp-7c9f8d6b5c-2plq9 1/1 Running 0 10m
myapp-7c9f8d6b5c-4k2pl 1/1 Running 0 10m
myapp-8f6d9c7b4d-9xqwt 0/1 ContainerCreating 0 5s
myapp-8f6d9c7b4d-vq7rd 1/1 Running 0 8s
Step 3: How maxSurge and maxUnavailable Change Behavior
With 6 replicas and the default 25%/25%, Kubernetes rounds up surge and rounds down unavailable, meaning during the rollout you could briefly have up to 8 Pods total (6 + 2 surge) while never dropping below 5 available (6 – 1 unavailable, rounded down from 1.5). Tuning these matters:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
Setting maxUnavailable: 0 guarantees you never drop below full capacity — useful for latency-sensitive services — at the cost of a slightly slower rollout since new Pods must become ready before old ones terminate. Conversely:
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 50%
maxUnavailable: 25%
This rolls out faster but tolerates more capacity reduction mid-rollout — reasonable for internal tools where a brief dip in capacity doesn’t matter.
Step 4: Readiness Probes Are Not Optional
Rolling updates rely entirely on readiness probes to know when a new Pod is safe to receive traffic and safe to consider “available” for the purpose of terminating an old Pod. Without one, Kubernetes considers a Pod ready the instant its container starts, which can send traffic to an application still initializing:
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
failureThreshold: 3
I’ve seen rollouts that “succeeded” per kubectl rollout status but actually served errors for the first several seconds of each new Pod’s life, purely because there was no readiness probe checking real application health.
Step 5: Rolling Back
If the new version is broken, Kubernetes keeps rollout history (controlled by revisionHistoryLimit, default 10), making rollback trivial:
kubectl rollout history deployment/myapp
REVISION CHANGE-CAUSE
1 kubectl apply --filename=myapp-deployment.yaml
2 kubectl set image deployment/myapp myapp=myrepo/myapp:1.1.0
kubectl rollout undo deployment/myapp
Or roll back to a specific revision:
kubectl rollout undo deployment/myapp --to-revision=1
This itself triggers a new rolling update, back to the old ReplicaSet’s spec — it’s not instant like blue-green, but it is automatic and safe.
Step 6: Pausing and Resuming Rollouts
For larger, riskier changes, you can pause a rollout partway through to observe behavior before continuing:
kubectl rollout pause deployment/myapp
kubectl set image deployment/myapp myapp=myrepo/myapp:1.2.0
# only a few pods update, then it stops
kubectl rollout resume deployment/myapp
This is a useful manual canary-like pattern without needing extra tooling, though for real canary analysis with automated metrics-based promotion, tools like Argo Rollouts or Flagger go further.
Step 7: CI/CD Integration
A typical deployment step in a pipeline, waiting for the rollout to actually succeed before marking the job green:
kubectl set image deployment/myapp myapp=myrepo/myapp:$CI_COMMIT_SHA
kubectl rollout status deployment/myapp --timeout=180s
If rollout status times out or fails (a CrashLoopBackOff from a bad image, for example), the pipeline step exits non-zero, and I’d immediately follow it with an automatic rollback:
if ! kubectl rollout status deployment/myapp --timeout=180s; then
echo "Rollout failed, rolling back"
kubectl rollout undo deployment/myapp
exit 1
fi
Debugging a Stuck Rollout
kubectl describe deployment myapp
kubectl get replicasets -l app=myapp
kubectl describe pod <new-pod-name>
A rollout stuck at “x out of y updated replicas” usually means new Pods aren’t passing their readiness probe — check logs and probe configuration on the specific failing Pod, not the Deployment as a whole.
Best Practices
- Always set readiness probes that reflect true application health, not just process liveness.
- Tune
maxUnavailableto 0 for anything customer-facing where capacity dips matter. - Set
revisionHistoryLimitdeliberately — too high wastes etcd storage with old ReplicaSets, too low limits how far back you can roll. - Combine with a PodDisruptionBudget to prevent rollouts (and voluntary node maintenance) from dropping availability below a safe threshold.
- Always test rollback in staging, not just the forward rollout — it’s a code path that only gets exercised during incidents if you haven’t rehearsed it.
Common Mistakes
- No readiness probe, leading to traffic hitting Pods before they’re actually ready.
- Assuming
kubectl rollout statusreturning success means the new version is actually healthy in production — it only confirms Pods became “Ready,” not that business logic is correct. - Ignoring
maxUnavailabledefaults on small replica counts, where 25% can round to zero, meaning updates happen one Pod at a time regardless of what you intended. - Forgetting that a rolling update briefly runs old and new versions simultaneously — incompatible API or database schema changes between versions will cause errors during that overlap window.
Graceful Shutdown During Rolling Updates
A rolling update terminates old Pods as new ones become ready, and how gracefully those old Pods shut down matters just as much as how new ones start up. When Kubernetes terminates a Pod, it sends SIGTERM, waits up to terminationGracePeriodSeconds (default 30), and then sends SIGKILL if the process hasn’t exited. If your application doesn’t handle SIGTERM by finishing in-flight requests and then exiting cleanly, rolling updates can silently drop requests even though every individual step “succeeded”:
spec:
template:
spec:
terminationGracePeriodSeconds: 45
containers:
- name: myapp
image: myrepo/myapp:1.1.0
lifecycle:
preStop:
exec:
command: ["sh", "-c", "sleep 5"]
The preStop hook here is a common pattern for services behind a Service/load balancer — it gives the endpoint controller time to remove the terminating Pod from load-balancing rotation before the process actually receives SIGTERM, closing a small but real race condition where a Pod could still receive a new request moments after it’s begun shutting down. Combined with your application code actually listening for SIGTERM and finishing (not accepting new, but completing existing) requests before exiting, this closes the loop on genuinely zero-downtime updates rather than just “no failed health checks” updates.
Rolling Updates for ConfigMap and Secret Changes
One gotcha worth knowing: updating a ConfigMap or Secret that a Deployment references does not automatically trigger a rolling update on its own — existing Pods keep running with the old mounted values (for volume-mounted ConfigMaps, they do eventually sync after a delay, but environment-variable-injected values never update without a Pod restart). If you need a config change to actually roll out, the common trick is annotating the Pod template with a hash of the config content, forcing Kubernetes to see the Pod spec as changed:
spec:
template:
metadata:
annotations:
checksum/config: "{{ configMapChecksum }}"
Tools like Reloader (a small controller you install once) automate this entirely — watching ConfigMaps and Secrets for changes and automatically triggering a rolling update on any Deployment that references them, without you having to manage checksums by hand:
metadata:
annotations:
reloader.stakater.com/auto: "true"
Monitoring a Rollout in Real Time
Beyond kubectl rollout status, I like watching actual request success rate during a rollout rather than just Pod readiness, since the two can diverge — a Pod can be “Ready” per its probe while still returning errors for a specific endpoint the probe doesn’t check. If you have Prometheus in place already:
sum(rate(http_requests_total{app="myapp", status=~"5.."}[1m]))
/
sum(rate(http_requests_total{app="myapp"}[1m]))
Watching this metric during a rollout — ideally on a dashboard you glance at while kubectl rollout status runs in another terminal — catches “technically ready, actually broken” releases that pure Pod-readiness monitoring misses entirely.
When Rolling Updates Aren’t the Right Choice
Rolling updates assume old and new versions can safely coexist, even briefly. That assumption breaks down in a few specific situations: when a database migration isn’t backward compatible with the previous code version, when a breaking API contract change means old and new replicas would behave inconsistently toward the same clients, or when the release is risky enough that you want the ability to instantly and completely revert rather than waiting for a second rolling update to undo the first. In any of those cases, blue-green deployment (see the dedicated guide) or a canary strategy with automated analysis (Argo Rollouts, Flagger) is a better fit than the default rolling strategy — it’s worth explicitly deciding which category a given release falls into rather than defaulting to rolling updates purely out of habit.
Summary
Rolling updates are Kubernetes’ built-in, zero-extra-tooling way to ship new versions with no downtime, gradually swapping old Pods for new ones according to maxSurge and maxUnavailable. They depend entirely on accurate readiness probes to work safely, and rollback is just as automated as rollout via revision history. For most day-to-day deployments, this strategy — tuned correctly — is all you need.