The Deployment is the most-used workload resource in Kubernetes. It manages ReplicaSets to provide declarative rolling updates, rollbacks, and version history — the features you need to ship code to production with zero downtime.

1. Deployment Anatomy

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  strategy:                         # ← how to roll out changes
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1                   # max extra Pods during update
      maxUnavailable: 0             # max Pods that can be down
  revisionHistoryLimit: 10          # ← how many old RSs to keep
  progressDeadlineSeconds: 600      # ← how long before marking failed
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: nginx
          image: nginx:1.25
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /
              port: 80
            initialDelaySeconds: 5
            periodSeconds: 5

Key Fields

FieldPurposeDefault
strategy.typeRollingUpdate or RecreateRollingUpdate
maxSurgeExtra Pods allowed above desired during rollout25%
maxUnavailablePods that can be unavailable during rollout25%
revisionHistoryLimitOld ReplicaSets retained (for rollback)10
progressDeadlineSecondsTime before rollout is considered stuck600 (10 min)
minReadySecondsTime a new Pod must be Ready before it's considered Available0
How it works internally: A Deployment doesn't manage Pods directly. It manages ReplicaSets. When you change the Pod template, the Deployment creates a new RS (with the new template) and gradually scales it up while scaling down the old RS. That's a rolling update.

2. Rolling Updates — The Math

Rolling updates are controlled by two parameters that define how much disruption is allowed:

maxSurge and maxUnavailable

ParameterMeaningCan Be
maxSurgeMax Pods above desired count at any timeAbsolute number or percentage
maxUnavailableMax Pods below desired count at any timeAbsolute number or percentage

Percentages are rounded up for maxSurge and rounded down for maxUnavailable.

Example: 10 replicas, maxSurge=2, maxUnavailable=1

Desired:          10 Pods
Max total Pods:   10 + 2 = 12  (can have up to 12 running)
Min available:    10 - 1 = 9   (at least 9 must be Ready)

Step 1: Scale new RS to 2 (total: 12 = 10 old + 2 new)  ✓ ≤12
Step 2: Scale old RS to 9 (total: 11 = 9 old + 2 new)   ✓ available ≥9
Step 3: New Pods ready → scale new to 4, old to 8...
... continues until old=0, new=10

Common Strategy Configurations

ConfigurationBehaviorUse Case
maxSurge=1, maxUnavailable=0Add one new, wait until Ready, then remove one oldZero-downtime (safest, slowest)
maxSurge=0, maxUnavailable=1Remove one old, wait for new to be ReadyNo extra resources (risks brief capacity loss)
maxSurge=25%, maxUnavailable=25%Fast parallel update (default)General purpose
maxSurge=100%, maxUnavailable=0Blue-green style — spin up all new, then drain oldWhen you need instant rollback capability
Rolling Update: 3 replicas, maxSurge=1, maxUnavailable=0 t=0 t=1 t=2 t=3 t=4 (done) old v1 old v1 old v1 total=3, avail=3 old v1 old v1 old v1 new v2 ⏳ total=4 (surge=1) old v1 old v1 new v2 ✓ new v2 ⏳ total=4, avail=3 old v1 new v2 ✓ new v2 ✓ new v2 ⏳ total=4, avail=3 new v2 ✓ new v2 ✓ new v2 ✓ total=3, avail=3 ✓
Readiness probes are essential for safe rollouts. The Deployment controller considers a new Pod "available" only when its readiness probe passes (and minReadySeconds have elapsed). Without a readiness probe, a Pod is "Ready" immediately after starting — meaning the rollout might proceed even if the app isn't actually serving traffic.

Recreate Strategy

The alternative to RollingUpdate: kill all old Pods first, then create all new Pods. Simple but causes downtime.

strategy:
  type: Recreate
# All old Pods terminated → all new Pods created
# Use case: app can't run two versions simultaneously (DB schema lock)
Use Recreate only when your app cannot have two versions running at the same time (e.g., it holds an exclusive lock, or old/new versions have incompatible DB schemas). For everything else, use RollingUpdate.

3. Rollbacks & Revision History

Each time you change the Pod template, the Deployment creates a new ReplicaSet (a new revision). Old ReplicaSets are kept (scaled to 0) for rollback.

Viewing History

# See all revisions:
kubectl rollout history deployment/web
# REVISION  CHANGE-CAUSE
# 1         kubectl apply --filename=deploy.yaml
# 2         kubectl set image deployment/web nginx=nginx:1.25
# 3         kubectl set image deployment/web nginx=nginx:1.26

# See details of a specific revision:
kubectl rollout history deployment/web --revision=2
# Shows the Pod template at that revision

Rolling Back

# Undo last change (rollback to revision N-1):
kubectl rollout undo deployment/web

# Rollback to specific revision:
kubectl rollout undo deployment/web --to-revision=1

# What happens internally:
# 1. Old RS (from target revision) is scaled up
# 2. Current RS is scaled down
# 3. The rollback becomes a NEW revision (appended, not overwritten)
Rollback = forward to old template. Undoing to revision 1 doesn't "revert history." It creates a new revision (4) with the same template as revision 1. Revision 1's RS is reused (scaled up). The revision counter always increases.

Setting CHANGE-CAUSE

# Record why a change was made (shows in rollout history):
kubectl annotate deployment/web kubernetes.io/change-cause="Upgrade nginx to 1.26"

# Or use --record (deprecated but still works):
kubectl set image deployment/web nginx=nginx:1.26 --record

revisionHistoryLimit

revisionHistoryLimit: 10 (default) means Kubernetes keeps 10 old ReplicaSets (scaled to 0). Older ones are garbage-collected. Set to 0 to disable rollback entirely (saves etcd space in large clusters).

In production with GitOps (ArgoCD/Flux), rollbacks are typically done by reverting the Git commit, not by kubectl rollout undo. The Deployment's revision history is still useful for quick incident response (undo in 2 seconds vs waiting for CI/CD), but the source of truth should always be Git.

4. Monitoring Rollouts

kubectl rollout status

# Watch a rollout in real-time:
kubectl rollout status deployment/web
# Waiting for deployment "web" rollout to finish:
#   2 of 3 updated replicas are available...
# deployment "web" successfully rolled out

# Exit code: 0 = success, non-zero = timeout/failure
# Useful in CI/CD pipelines to gate the next step

Deployment Conditions

kubectl get deployment web -o yaml | grep -A10 conditions:
# conditions:
# - type: Available
#   status: "True"
#   reason: MinimumReplicasAvailable
# - type: Progressing
#   status: "True"
#   reason: NewReplicaSetAvailable
ConditionStatus: True meansStatus: False means
AvailableAt least minAvailable Pods are ReadyNot enough Pods Ready
ProgressingRollout is making progress or completeStuck beyond progressDeadlineSeconds

Stuck Rollouts

If a rollout makes no progress for progressDeadlineSeconds (default 600s), the condition Progressing becomes False with reason ProgressDeadlineExceeded. This does NOT auto-rollback — it's just a signal.

# Common causes of stuck rollouts:
# - New image doesn't exist (ImagePullBackOff)
# - Readiness probe never passes (app crashes on startup)
# - Insufficient resources (Pod stuck Pending)
# - Quota exceeded

# Fix: undo the rollout
kubectl rollout undo deployment/web

Pausing and Resuming

# Pause — make multiple changes without triggering a rollout for each:
kubectl rollout pause deployment/web
kubectl set image deployment/web nginx=nginx:1.26
kubectl set resources deployment/web -c nginx --limits=memory=256Mi
kubectl rollout resume deployment/web
# Single rollout triggered with both changes
kubectl rollout pause/resume is tested in CKA. Know that while paused, changes accumulate but no rollout happens. Resuming triggers one consolidated rollout. You can't rollback while paused.

5. Production Patterns

Triggering Rollouts Without Code Changes

Sometimes you need to restart all Pods (e.g., to pick up a rotated Secret):

# Restart all Pods (creates new RS with same template + annotation change):
kubectl rollout restart deployment/web

# Under the hood: adds annotation 
# kubectl.kubernetes.io/restartedAt: "2024-01-15T10:30:00Z"
# This changes the template → triggers rolling update

Canary Deployments (Manual)

# Approach: pause rollout after partial scale-up
kubectl set image deployment/web nginx=nginx:1.26
# Immediately pause when a few new Pods are running:
kubectl rollout pause deployment/web

# Monitor canary Pods, check metrics/logs
# If good: resume
kubectl rollout resume deployment/web
# If bad: undo
kubectl rollout undo deployment/web
For real canary deployments, use dedicated tools: Argo Rollouts, Flagger, or Istio traffic splitting. The pause trick gives you a basic canary but with no traffic weighting — all Pods receive equal traffic. Real canaries route only 5-10% of traffic to new Pods.

Blue-Green with Deployments

# Strategy: maxSurge=100%, maxUnavailable=0
# Effect: all new Pods come up BEFORE any old Pods are removed
strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: "100%"      # double the Pods temporarily
    maxUnavailable: 0     # zero downtime guaranteed

# Total Pods during rollout: up to 2x desired
# Requires double the node capacity temporarily

Config Change Triggers

Changing a ConfigMap doesn't trigger a Deployment rollout (Pods reference ConfigMaps, but the Pod template doesn't change). Common pattern to force a rollout on config change:

# Add a hash of the config as an annotation:
spec:
  template:
    metadata:
      annotations:
        configHash: "sha256:abc123..."  # computed by CI/CD

# When ConfigMap changes → hash changes → template changes → rollout triggered

6. Deployment vs Directly Managing Pods

FeatureBare PodReplicaSetDeployment
Self-healing
Scaling
Rolling updates
Rollback
Revision history
Pause/resume

Summary

ConceptKey Point
Rolling updateNew RS scales up, old RS scales down — controlled by maxSurge/maxUnavailable
maxSurgeHow many extra Pods above desired (controls speed vs resource cost)
maxUnavailableHow many Pods below desired (controls acceptable downtime)
RevisionEach template change = new revision (old RS kept at scale 0)
Rollbackrollout undo — creates new revision with old template
Readiness probeEssential — without it, rollout proceeds even if new Pods are broken
progressDeadlineSecondsHow long to wait before marking rollout as stuck

📝 Quiz: Deployments

Q1: A Deployment has 4 replicas, maxSurge=50%, maxUnavailable=25%. What's the max and min number of Pods during rollout?

Max: 6 Pods (4 + ceil(50% × 4) = 4 + 2 = 6).
Min available: 3 Pods (4 - floor(25% × 4) = 4 - 1 = 3).
So during rollout, between 3 and 6 Pods will exist at any time.

Q2: You set maxSurge=0, maxUnavailable=0. What happens?

Invalid — rejected by the API server. At least one of maxSurge or maxUnavailable must be non-zero, otherwise the rollout can never make progress (can't add new Pods and can't remove old Pods).

Q3: A new Pod's readiness probe fails for 10 minutes. The rollout has maxSurge=1, maxUnavailable=0. What happens?

The rollout stalls. With maxUnavailable=0, no old Pod can be removed until a new Pod is Ready. Since readiness fails, the new Pod is never "available," so the rollout makes no progress. After progressDeadlineSeconds (default 600s), the Progressing condition becomes False. No auto-rollback — you must intervene.

Q4: You roll back from revision 5 to revision 3. What revision number does the rollback create?

Revision 6. Rollback doesn't rewrite history — it creates a new revision with the template from revision 3. The Deployment reuses revision 3's ReplicaSet (scales it up). The counter always moves forward.

Q5: You update a ConfigMap that a Deployment's Pods reference. Do the Pods restart?

No. ConfigMap changes don't modify the Pod template, so no rollout is triggered. Existing Pods may see the new ConfigMap data (if mounted as a volume, it eventually syncs), but no restart happens. For a guaranteed restart, either use kubectl rollout restart or add a config hash annotation to the Pod template.

Q6: What's the advantage of maxSurge=1, maxUnavailable=0 vs the default 25%/25%?

Zero downtime guarantee. With maxUnavailable=0, the total available Pods never drops below the desired count. The trade-off: it's slower (one Pod at a time) and requires spare node capacity (one extra Pod during rollout). The 25%/25% default is faster but allows 25% capacity reduction temporarily.