Kubernetes Deployments, Rolling Updates and Rollbacks

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster
Cert prep CKA · CKAD
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope Deployment YAML, imperative create, scaling, image updates, RollingUpdate strategy, maxSurge and maxUnavailable, rollout status, pause and resume, revision history, rollback, Deployment conditions, and common rollout problems. Does not cover HPA, blue-green or canary procedures, StatefulSet or DaemonSet rollouts, GitOps, Helm, readiness probe depth, PodDisruptionBudget, or progressive delivery controllers.

This walkthrough uses the deploy-lab namespace and one nginx Deployment named web. You will create and scale it, roll out nginx:1.27.0 from nginx:1.25.4, pause the Deployment before a template change, batch updates, and resume one rollout, then inspect revision history and roll back a failed release.

All rollout exercises use kubectl set image and related imperative commands so the revision sequence is reproducible. Do not also edit or reapply web-deploy.yaml during the rollout and rollback exercises.

Imperative image, environment, pause/resume, and rollback commands change the live Deployment but do not update web-deploy.yaml. Reapplying the unchanged file later can restore its old image, replica count, and Pod-template configuration.


What Is a Kubernetes Deployment?

A Deployment manages interchangeable application Pods for long-running stateless workloads. It creates and manages ReplicaSets, maintains the desired replica count, and supports scaling, rolling updates, and rollbacks.

The ownership chain:

Deployment → ReplicaSet → Pods

For when to pick a bare Pod versus a Deployment, see Pod versus Deployment. For how ReplicaSets maintain Pod counts before rollout features, see Kubernetes ReplicaSet.


Create and Understand a Deployment

Imperative creation

For a quick one-replica Deployment, imperative create is enough:

bash
kubectl create namespace deploy-lab

Sample output:

output
namespace/deploy-lab created
bash
kubectl create deployment quick --image=nginx:1.25.4 -n deploy-lab

Sample output:

output
deployment.apps/quick created
bash
kubectl wait --for=condition=Available deployment/quick -n deploy-lab --timeout=120s

Sample output:

output
deployment.apps/quick condition met

A Deployment becomes Available when enough replicas are Ready and have remained Ready for minReadySeconds. The default minReadySeconds is zero.

bash
kubectl get deployments -n deploy-lab

Sample output:

output
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
quick   1/1     1            1           3s

Delete the quick Deployment before the main YAML lab:

bash
kubectl delete deployment quick -n deploy-lab --wait=true

Sample output:

output
deployment.apps "quick" deleted

Deployment YAML

Save this manifest as web-deploy.yaml:

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: deploy-lab
  labels:
    app: web
spec:
  replicas: 3
  revisionHistoryLimit: 5
  progressDeadlineSeconds: 60
  selector:
    matchLabels:
      app: web
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.25.4
  • spec.replicas sets how many Pods the Deployment should run
  • revisionHistoryLimit: 5 retains old ReplicaSets for rollback
  • progressDeadlineSeconds: 60 lets the lab surface a failed rollout quickly
  • spec.selector.matchLabels must match spec.template.metadata.labels
  • The container image uses an explicit version tag so updates are visible later
bash
kubectl apply -f web-deploy.yaml

Sample output:

output
deployment.apps/web created

Selector, Pod template, and ReplicaSet ownership

  • The Deployment spec.selector identifies Pods it manages
  • Labels on spec.template.metadata.labels must match that selector; Deployment selectors are immutable after creation
  • Kubernetes also adds pod-template-hash on each ReplicaSet and its Pods; that hash is not part of your Deployment selector
  • Changes under spec.template create a new rollout and ReplicaSet when the Deployment is active; while paused, Kubernetes waits until the Deployment is resumed
  • Editing Deployment metadata outside the Pod template, such as metadata.labels on the Deployment object, does not trigger a rollout

Verify Pods and ReplicaSets

Wait until the Deployment reports Available, then confirm the replica counts:

bash
kubectl wait --for=condition=Available deployment/web -n deploy-lab --timeout=120s
bash
kubectl get deployments -n deploy-lab

Sample output:

output
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    3/3     3            3           5s

Column meanings on kubectl get deployments:

  • READY — ready replicas versus desired (3/3)
  • UP-TO-DATE — Pods running the latest Pod template
  • AVAILABLE — replicas that satisfy the Deployment's availability requirement
bash
kubectl get replicasets -n deploy-lab

Sample output:

output
NAME             DESIRED   CURRENT   READY   AGE
web-86b6cb7b94   3         3         3       5s
bash
kubectl get pods -n deploy-lab -l app=web

Sample output:

output
NAME                   READY   STATUS    RESTARTS   AGE
web-86b6cb7b94-dgqxl   1/1     Running   0          5s
web-86b6cb7b94-f4hx7   1/1     Running   0          4s
web-86b6cb7b94-nmp5s   1/1     Running   0          4s
bash
kubectl describe deployment web -n deploy-lab

Sample output (trimmed):

output
Replicas:               3 desired | 3 updated | 3 total | 3 available | 0 unavailable
RollingUpdateStrategy:  0 max unavailable, 1 max surge
    Image:         nginx:1.25.4
NewReplicaSet:   web-86b6cb7b94 (3/3 replicas created)

This initial Pod template is revision 1 with nginx:1.25.4.


Scale a Deployment

Imperative scaling

Increase the Deployment to four replicas. Scaling edits spec.replicas on the live object and does not change the Pod template, so rollout revision history stays at revision 1.

bash
kubectl scale deployment web -n deploy-lab --replicas=4

Sample output:

output
deployment.apps/web scaled

Wait until four Pods report Ready before you continue.

bash
kubectl wait --for=jsonpath='{.status.readyReplicas}'=4 deployment/web -n deploy-lab --timeout=120s

Confirm the Deployment status columns reflect the new count:

bash
kubectl get deployment web -n deploy-lab

Sample output:

output
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    4/4     4            4           7s

Four Pods now match the desired count. Scaling alone does not create a new rollout revision.

Reapply declarative replica count

kubectl scale changed the live Deployment to four replicas but did not update web-deploy.yaml. Reapply the unchanged manifest to restore its declared replica count of three.

bash
kubectl apply -f web-deploy.yaml

Sample output:

output
deployment.apps/web configured

Wait until the scale-down completes and three Pods are Ready again.

bash
kubectl wait --for=jsonpath='{.status.readyReplicas}'=3 deployment/web -n deploy-lab --timeout=120s

Confirm the Deployment returned to three replicas:

bash
kubectl get deployment web -n deploy-lab

Sample output:

output
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    3/3     3            3           8s

If you scale imperatively and later reapply a manifest with a different replicas value, the manifest becomes the desired state again. Horizontal Pod Autoscaler is outside this article.


Roll Out a New Application Version

Update the image

Roll the Deployment to nginx:1.27.0 with an imperative image change:

bash
kubectl set image deployment/web nginx=nginx:1.27.0 -n deploy-lab

Sample output:

output
deployment.apps/web image updated

A Pod-template change starts a new rollout. This becomes revision 2.

Watch old and new ReplicaSets

Immediately watch ReplicaSets and Pods:

bash
kubectl get rs,pod -n deploy-lab -l app=web --watch

Sample output (trimmed) during the rollout:

output
NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/web-5b6bd7f99b   2         2         1       4s
replicaset.apps/web-86b6cb7b94   2         2         2       12s

NAME                       READY   STATUS              RESTARTS   AGE
pod/web-5b6bd7f99b-lk96n   0/1     ContainerCreating   0          2s
pod/web-5b6bd7f99b-q6bcm   1/1     Running             0          4s
pod/web-86b6cb7b94-dgqxl   1/1     Running             0          12s
pod/web-86b6cb7b94-f4hx7   1/1     Terminating         0          11s
pod/web-86b6cb7b94-nmp5s   1/1     Running             0          11s

Observe the new ReplicaSet scaling up and the old one scaling down. Press Ctrl+C after the new ReplicaSet reaches three Ready Pods.

kubectl rollout status follows the latest rollout until it completes, so running it first removes the intermediate state this section demonstrates.

Confirm the deployed template

Confirm the rollout completed successfully:

bash
kubectl rollout status deployment/web -n deploy-lab --timeout=120s

Sample output (trimmed):

output
Waiting for deployment "web" rollout to finish: 2 out of 3 new replicas have been updated...
Waiting for deployment "web" rollout to finish: 1 old replicas are pending termination...
deployment "web" successfully rolled out

List ReplicaSets to confirm the old set scaled to zero:

bash
kubectl get replicasets -n deploy-lab

Sample output:

output
NAME             DESIRED   CURRENT   READY   AGE
web-5b6bd7f99b   3         3         3       7s
web-86b6cb7b94   0         0         0       15s

Read the image from the live Deployment template:

bash
kubectl describe deployment web -n deploy-lab | grep 'Image:'

Sample output:

output
Image:         nginx:1.27.0

Or read the template image directly:

bash
kubectl get deployment web -n deploy-lab -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

Sample output:

output
nginx:1.27.0

Configure Deployment Update Strategy

RollingUpdate

RollingUpdate is the default Deployment update strategy. It replaces Pods incrementally instead of deleting all old Pods first.

yaml
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 1
    maxUnavailable: 0
  • maxSurge and maxUnavailable accept absolute numbers or percentages
  • maxUnavailable percentages are rounded down
  • maxSurge percentages are rounded up
  • Both fields cannot resolve to zero at the same time
  • Defaults are 25% for each when not specified

maxSurge and maxUnavailable

Field Controls
maxSurge Extra Pods allowed above the desired replica count
maxUnavailable Desired Pods allowed to be unavailable

Practical presets:

  • Availability-focused: maxSurge: 1, maxUnavailable: 0 — keep capacity while surging one extra Pod
  • Capacity-constrained: maxSurge: 0, maxUnavailable: 1 — no surge; replace in place one Pod at a time

maxSurge controls how many extra Pods may run above the desired replica count during an update. With four desired replicas and maxSurge: 1, the cluster may briefly run five Pods while one new Pod starts before an old Pod terminates.

maxUnavailable controls how many desired Pods may be unavailable during the rollout. With maxSurge: 0 and maxUnavailable: 1, Kubernetes must first remove an old Pod before creating its replacement.

Example with four replicas and maxSurge: 0, maxUnavailable: 1:

Stage Old Pods New Pods Total non-terminating Pods Available Pods
Start 4 0 4 4
Scale down one old Pod 3 0 3 3
Start one new Pod 3 1 4 3 until the new Pod is Ready
New Pod becomes Ready 3 1 4 4
Repeat Decreases Increases 3 or 4 At least 3
Complete 0 4 4 4

No non-terminating Pod count exceeds the desired four replicas. During each replacement, availability may temporarily fall to three until the new Pod becomes Ready.

Recreate strategy

Kubernetes also supports Recreate:

  • Terminates all old Pods before starting new ones, causing brief downtime
  • Fits maintenance windows or workloads that cannot run two versions side by side

For broader workload and strategy selection, see choose a Kubernetes workload resource. Blue-green and canary patterns use separate Deployments and Service selectors — see Kubernetes blue-green and canary deployments.

Terminating Pods during rollout

  • Terminating Pods are not counted the same way as active rollout replicas
  • Their grace periods can make the total number of Pod objects—and actual resource use—temporarily exceed replicas + maxSurge
  • Kubernetes 1.36 can expose these through .status.terminatingReplicas when the beta feature is enabled, which it is by default

These are part of current Deployment rollout accounting.


Pause and Resume a Deployment

Batch Pod-template changes

Revision 3 in this lab starts from a paused template change rather than an image revert.

bash
kubectl rollout pause deployment/web -n deploy-lab

Sample output:

output
deployment.apps/web paused

Make a different Pod-template change instead of reverting the image:

bash
kubectl set env deployment/web RELEASE_STAGE=paused-demo -n deploy-lab

Sample output:

output
deployment.apps/web env updated

While paused, Pod-template edits are stored on spec.template, but they do not create a new ReplicaSet or rollout revision. A paused Deployment cannot be rolled back until it is resumed.

Verify paused state

bash
kubectl rollout history deployment/web -n deploy-lab

Sample output:

output
deployment.apps/web 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>

Rollout history is unchanged while the Deployment stays paused.

bash
kubectl get deployment web -n deploy-lab

Sample output:

output
NAME   READY   UP-TO-DATE   AVAILABLE   AGE
web    3/3     0            3           16s

AVAILABLE stays at three because old Pods still run, but UP-TO-DATE is 0 because the new template has not rolled out.

bash
kubectl get deployment web -n deploy-lab -o jsonpath='Paused: {.spec.paused}{"\n"}'

Sample output:

output
Paused: true

Resume one rollout

bash
kubectl rollout resume deployment/web -n deploy-lab

Sample output:

output
deployment.apps/web resumed

Wait for the resumed rollout to finish:

bash
kubectl rollout status deployment/web -n deploy-lab --timeout=120s

The resumed template change becomes revision 3. Kubernetes does not start a rollout for Pod-template edits while a Deployment remains paused.

bash
kubectl rollout history deployment/web -n deploy-lab --revision=3

Sample output (trimmed):

output
deployment.apps/web with revision #3
Pod Template:
  Labels:	app=web
	pod-template-hash=6786685cc6
  Containers:
   nginx:
    Image:	nginx:1.27.0
    Environment:
      RELEASE_STAGE:	paused-demo

Inspect Revisions and Roll Back

View revision history

bash
kubectl rollout history deployment/web -n deploy-lab

Sample output:

output
deployment.apps/web 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
3         <none>

Each Pod-template change creates a new revision. Scaling alone does not add a revision.

Create a failed release

Create a deliberately failed rollout:

bash
kubectl set image deployment/web nginx=nginx:not-a-real-tag -n deploy-lab

Sample output:

output
deployment.apps/web image updated
bash
kubectl rollout status deployment/web -n deploy-lab --timeout=75s

Sample output:

output
error: deployment "web" exceeded its progress deadline

Inspect the new ReplicaSet and failing Pod before rollback:

bash
kubectl get rs,pod -n deploy-lab -l app=web

Sample output:

output
NAME                             DESIRED   CURRENT   READY   AGE
replicaset.apps/web-54d5c4cc5c   1         1         0       61s
replicaset.apps/web-6786685cc6   3         3         3       68s

NAME                       READY   STATUS         RESTARTS   AGE
pod/web-54d5c4cc5c-ff6r9   0/1     ErrImagePull   0          61s
pod/web-6786685cc6-7g9jj   1/1     Running        0          68s
pod/web-6786685cc6-npktg   1/1     Running        0          64s
pod/web-6786685cc6-tkfvz   1/1     Running        0          66s

With maxUnavailable: 0, the three previous Pods remain available while the bad ReplicaSet has one surge Pod in ErrImagePull or ImagePullBackOff. This becomes revision 4.

Roll back to the previous revision

bash
kubectl rollout undo deployment/web -n deploy-lab

Sample output:

output
deployment.apps/web rolled back

This restores revision 3's template and creates a new current revision, normally revision 5.

Wait for the rollback rollout to finish:

bash
kubectl rollout status deployment/web -n deploy-lab --timeout=120s

List the revision numbers again after the rollback:

bash
kubectl rollout history deployment/web -n deploy-lab

Sample output:

output
deployment.apps/web 
REVISION  CHANGE-CAUSE
1         <none>
2         <none>
4         <none>
5         <none>

A rollback restores an older Pod template as a new current revision; revision numbers and the entries shown in history therefore change after rollback. Always rerun rollout history before using --to-revision.

bash
kubectl describe deployment web -n deploy-lab | grep 'Image:'

Sample output:

output
Image:         nginx:1.27.0

The Pod template returned to revision 3's image and environment variables.

Roll back to a specific revision

Revision 2 and revision 3 both use nginx:1.27.0; their difference is the RELEASE_STAGE environment variable. Rolling back to revision 1 makes the result visible because it restores the original nginx:1.25.4 image.

Inspect history again and select a revision that is still listed:

bash
kubectl rollout history deployment/web -n deploy-lab

Roll back to revision 1:

bash
kubectl rollout undo deployment/web -n deploy-lab --to-revision=1

Sample output:

output
deployment.apps/web rolled back

This restores the original revision 1 template with nginx:1.25.4.

Wait for that rollout to complete:

bash
kubectl rollout status deployment/web -n deploy-lab --timeout=120s

Confirm the container image on the live template:

bash
kubectl get deployment web -n deploy-lab -o jsonpath='{.spec.template.spec.containers[0].image}{"\n"}'

Sample output:

output
nginx:1.25.4

Rollback restores the selected Pod template. It does not revert unrelated manual scaling performed outside the template.

revisionHistoryLimit

  • revisionHistoryLimit: 5 retains up to five old ReplicaSets after completed rollouts
  • It does not count the active ReplicaSet
  • Cleanup happens only after the Deployment reaches a complete state, so a stuck Deployment may temporarily retain more old ReplicaSets than the configured limit
  • Setting the value to 0 removes rollback history

Deployment history is stored in its ReplicaSets; once one is cleaned up, that revision can no longer be restored.


Monitor and Troubleshoot Rollouts

Deployment conditions

kubectl describe deployment reports conditions such as:

  • Available — minimum replicas satisfy the Deployment's availability requirement
  • Progressing — rollout is active or completed successfully
  • ReplicaFailure — replica creation failed

Sample output (trimmed):

output
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Available      True    MinimumReplicasAvailable
  Progressing    True    NewReplicaSetAvailable

No Service is created in this article, so Kubernetes is not verifying that the Pods are actually receiving traffic.

Progress deadline

  • progressDeadlineSeconds controls when Kubernetes reports that the rollout has stopped making progress
  • It does not terminate the rollout or automatically roll it back; the Deployment controller continues retrying
  • Time spent with the Deployment paused is not counted toward the progress deadline

When the deadline is exceeded, the condition becomes:

output
type: Progressing
status: "False"
reason: ProgressDeadlineExceeded

Common rollout problems

Symptom Likely cause Fix
Rollout waiting for new Pods Pods not Ready Inspect Pod status and Events; see Kubernetes Pods and Pod Lifecycle
ProgressDeadlineExceeded New Pods never become healthy Check the new ReplicaSet Pods; roll back if the release is bad
Deployment not creating Pods Zero replicas, selector mismatch, quota, or admission error Fix Deployment not creating Pods
Old ReplicaSet not scaling down New Pods not becoming Ready Check readiness and rollout availability settings; see Kubernetes health probes
Image update did not roll out Image changed outside spec.template.spec.containers Edit the Pod template container image, not unrelated fields
Reapply restored old image or replica count Manifest is declarative desired state Update the YAML before kubectl apply, or align imperative changes with the manifest
Image pull failures on new Pods Wrong tag, registry auth, or network Verify image name and registry access

For Pod scheduling, image pull, and ContainerCreating problems, inspect individual Pods with kubectl describe pod and Events.


What's Next

References

Summary

You created a Deployment, verified ReplicaSet and Pod ownership, scaled replicas, and rolled out a new nginx image with RollingUpdate. maxSurge and maxUnavailable trade rollout speed against capacity during the change. kubectl rollout status, rollout history, and ReplicaSet lists show where a release stands.

Pause and resume let you batch Pod-template edits before they hit the cluster. The lab paused before an environment-variable change, resumed one rollout as revision 3, created a failed image release, rolled back to the previous healthy template, and restored revision 1 with --to-revision=1 to return to nginx:1.25.4. Watch UP-TO-DATE and AVAILABLE together during rollouts, especially when a Deployment is paused or new Pods fail readiness checks.

StatefulSet and DaemonSet rollouts follow different controller rules and are not covered here.


Frequently Asked Questions

1. What triggers a new Kubernetes Deployment rollout?

When a Deployment is active, changing spec.template, such as its container image, environment, or template labels, starts a rollout. While paused, template changes are stored and reconciled together after the Deployment is resumed. Scaling or changing Deployment metadata outside the Pod template does not create a revision.

2. What is the difference between maxSurge and maxUnavailable?

maxSurge sets how many extra Pods above the desired replica count can exist during an update. maxUnavailable sets how many desired Pods may be unavailable. Together they control rollout speed versus availability.

3. How do I roll back a Kubernetes Deployment?

Run kubectl rollout undo deployment/name to return to the previous revision, or add --to-revision=N for a specific entry from kubectl rollout history. Rollback restores the previous Pod template, not unrelated manual scaling.

4. Why is my Deployment rollout stuck?

Inspect new ReplicaSet Pods for scheduling, image pull, or container start failures. Check readiness, resource quotas, and Deployment Events. ProgressDeadlineExceeded means the rollout did not become healthy within progressDeadlineSeconds.

5. Does scaling create a new Deployment revision?

No. Changing spec.replicas alone does not create a new rollout revision. Only Pod template changes recorded in rollout history create new revisions and ReplicaSets.

6. What happens when I pause a Deployment rollout?

While paused, Pod-template edits are stored on spec.template, but they do not create a new ReplicaSet or rollout revision. When you resume the Deployment, Kubernetes reconciles the combined template changes in one rollout.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)