# Kubernetes Pods and Deployments: Replicas, Rolling Updates and Rollbacks

> Learn what a Pod, ReplicaSet and Deployment are, write a manifest, scale, update with zero downtime, roll back and fix the common errors, with an FAQ.

- Source: https://www.itwonderlab.com/kubernetes-deployments-pods-rolling-updates/
- Published: 2026-09-01
- Updated: 2026-09-01
- Author: Javier Ruiz Jiménez (https://www.javierruizjimenez.com/)
- Site: IT Wonder Lab (https://www.itwonderlab.com/)

---

## Pods, ReplicaSets and Deployments

A [Pod](https://www.itwonderlab.com/kubernetes-pod/) is the smallest thing you can run in [Kubernetes](https://www.itwonderlab.com/kubernetes/): one or more containers that share an IP address, ports and volumes. Pods are disposable. If a node dies, its pods are gone and nobody recreates them, unless a controller owns them.

- A **ReplicaSet** keeps N identical pods running.
- A [Kubernetes Deployment](https://www.itwonderlab.com/kubernetes-deployment/) manages ReplicaSets. When you change the image, it creates a new ReplicaSet and moves pods from the old one to the new one, a **rolling update**. It also remembers the old ReplicaSets so you can roll back.

You almost never create pods or ReplicaSets by hand: you create a Deployment (for stateless apps), a StatefulSet (databases), a DaemonSet (one pod per node) or a Job/CronJob (batch). You need a cluster: [K3s](https://www.itwonderlab.com/install-kubernetes-k3s/), [kind](https://www.itwonderlab.com/kind-local-kubernetes-cluster/) or [Rancher Desktop](https://www.itwonderlab.com/rancher-desktop-kubernetes/).

![Rolling update of a Deployment: the Deployment creates a new ReplicaSet for version 2, scales it up while the old ReplicaSet for version 1 scales down, and keeps the old one with zero replicas for rollback](https://www.itwonderlab.com/media/tutorials/Diagrams/ITWL-K8s-Deployment-Rollout.svg "A rolling update moves pods from the old ReplicaSet to the new one")

## Create a Deployment

```yaml title="web.yaml"
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  labels:
    app: web
spec:
  replicas: 3
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      app: web            # must match the pod template labels, and cannot change later
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1         # one extra pod during the update
      maxUnavailable: 0   # never go below 3 ready pods
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80
          resources:
            requests: { cpu: 50m, memory: 64Mi }
            limits: { memory: 128Mi }
          readinessProbe:
            httpGet: { path: /, port: 80 }
```

```bash
kubectl apply -f web.yaml
kubectl get deployment web
kubectl get pods -l app=web -o wide
kubectl get rs -l app=web
```

The pod names have the shape `web-<replicaset hash>-<random>`. The `READY 3/3` of the Deployment means 3 pods are ready to receive traffic. To reach them you need a Service, see [Services and Ingress](https://www.itwonderlab.com/kubernetes-services-ingress/). For a quick test: `kubectl port-forward deploy/web 8080:80`.

Always set `resources.requests` (the scheduler uses them) and a `readinessProbe` (the rolling update waits for it). Both are covered in [Probes and resource limits](https://www.itwonderlab.com/kubernetes-probes-resource-limits/).

## Create a manifest without writing it

```bash
kubectl create deployment web --image=nginx:1.27 --replicas=3 --dry-run=client -o yaml > web.yaml
kubectl run tmp --image=busybox:1.36 --rm -it --restart=Never -- sh      # throwaway pod
kubectl explain deployment.spec.strategy
```

`--dry-run=client -o yaml` generates a correct file to edit. `kubectl apply --dry-run=server -f web.yaml` validates it against the real API without saving.

## Scale

```bash
kubectl scale deployment web --replicas=5
kubectl get deploy web -w
```

In production, prefer to let the [Horizontal Pod Autoscaler](https://www.itwonderlab.com/kubernetes-horizontal-pod-autoscaler/) decide, and then do not set `replicas` by hand in Git (Argo CD and `kubectl apply` would fight the autoscaler).

## Rolling update

Change the image, either editing the YAML and running `kubectl apply`, or with:

```bash
kubectl set image deployment/web web=nginx:1.27.2
kubectl rollout status deployment/web
kubectl get rs -l app=web
```

The sequence with `maxSurge: 1` and `maxUnavailable: 0`: create 1 new pod, wait until it is Ready, terminate 1 old pod, repeat. If the new pods never become Ready, the update **stops** and the old pods keep serving. That is the safety net, and also why a broken readiness probe shows up as a stuck rollout.

Only changes in `spec.template` start a rollout. Changing `replicas` does not.

```bash
kubectl rollout restart deployment/web     # new pods with the same image (for example to reload a ConfigMap)
```

## Rollback

```bash
kubectl rollout history deployment/web
kubectl rollout history deployment/web --revision=2
kubectl rollout undo deployment/web                  # previous revision
kubectl rollout undo deployment/web --to-revision=1
kubectl annotate deployment/web kubernetes.io/change-cause="upgrade to nginx 1.27.2"
```

`revisionHistoryLimit` decides how many old ReplicaSets are kept (default 10). Set the `change-cause` annotation (or use Git) so the history is readable. Remember that a rollback only restores the pod template, not a ConfigMap or database migration.

Pause a rollout to batch several changes: `kubectl rollout pause deployment/web`, then `kubectl rollout resume deployment/web`.

## Strategies

| Strategy | Behavior | Use it for |
|---|---|---|
| `RollingUpdate` (default) | Replaces pods gradually | Almost everything |
| `Recreate` | Kills all old pods, then starts the new ones. Short downtime | Apps that cannot run two versions at once, or a `ReadWriteOnce` volume |
| Blue/green, canary | Not built in. Use two Deployments with a Service or a mesh such as [Istio](https://www.itwonderlab.com/istio-patterns-traffic-splitting-in-kubernetes/) | Controlled traffic shifts |

## Graceful shutdown

When a pod is deleted, Kubernetes sends `SIGTERM`, waits `terminationGracePeriodSeconds` (30 s default) and then sends `SIGKILL`. At the same time the pod is removed from the Service endpoints, but that takes a moment, so some traffic can still arrive. To avoid failed requests:

```yaml
    spec:
      terminationGracePeriodSeconds: 45
      containers:
        - name: web
          lifecycle:
            preStop:
              exec:
                command: ["sleep", "10"]
```

Also make the application handle `SIGTERM`: stop accepting connections and finish requests in flight. Run the app as PID 1 (`exec` in shell scripts) or the signal never arrives.

## Common errors

| Symptom | Cause | Fix |
|---|---|---|
| `Invalid value: ... selector does not match template labels` | `spec.selector.matchLabels` differs from `template.metadata.labels` | Make them identical |
| `field is immutable` when changing the selector | The selector of a Deployment cannot be changed | Delete and recreate the Deployment |
| Pods `Pending` | No node fits the requests, or no storage | [Pod stuck in Pending](https://www.itwonderlab.com/kubernetes-pod-pending/) |
| `ImagePullBackOff`, `ErrImagePull` | Wrong image, tag or credentials | [ImagePullBackOff](https://www.itwonderlab.com/kubernetes-imagepullbackoff/) |
| `CrashLoopBackOff` | The container exits | [CrashLoopBackOff](https://www.itwonderlab.com/kubernetes-crashloopbackoff/) |
| `Waiting for deployment "web" rollout to finish: 1 out of 3 new replicas have been updated...` for a long time | New pods are not Ready (probe, crash, resources) | `kubectl describe pod`, `kubectl logs`, `kubectl rollout undo` |
| `error: deployment "web" exceeded its progress deadline` (`ProgressDeadlineExceeded`) | The rollout did not progress in `progressDeadlineSeconds` (600 s) | Same as above. The old pods still serve |
| `no matches for kind "Deployment" in version "extensions/v1beta1"` | Removed API version | Use `apps/v1` |
| Rollout does not start after editing a ConfigMap | Only the pod template triggers it | `kubectl rollout restart`, or add a hash annotation (Helm `checksum/config`) |
| `forbidden: exceeded quota` | A ResourceQuota in the namespace | `kubectl describe quota` |

## How to debug a Deployment

```bash
kubectl get deploy,rs,pods -l app=web
kubectl describe deployment web            # Conditions: Available, Progressing, ReplicaFailure
kubectl describe rs <name>                 # Events: FailedCreate (quota, security admission)
kubectl describe pod <name>                # Events: scheduling, pulling, probes
kubectl logs deploy/web --all-containers --tail=100
kubectl logs <pod> --previous
kubectl get events --sort-by=.lastTimestamp
```

Read it top to bottom: Deployment, ReplicaSet, Pod, container. If the ReplicaSet exists but has no pods, the problem is in its events. If the pods exist, it is in theirs. The full method is in [How to debug Kubernetes](https://www.itwonderlab.com/how-to-debug-kubernetes/).

## Frequently asked questions

**What is the difference between a Pod and a Deployment?** A Pod runs containers once. A Deployment keeps a desired number of pods running and updates them in a controlled way.

**What is the difference between a Deployment and a StatefulSet?** A StatefulSet gives each pod a stable name (`db-0`, `db-1`), an ordered start and its own volume. Use it for databases. A Deployment pods are interchangeable.

**Can I run several containers in a Pod?** Yes, for tightly coupled helpers (sidecars): a log shipper, a proxy. They share localhost and volumes. Do not put the web app and the database in one pod, because they scale together.

**How do I update without downtime?** Use `RollingUpdate` with a `readinessProbe`, at least 2 replicas and `maxUnavailable: 0`. Add a `PodDisruptionBudget` so node drains do not remove every pod at once.

**Why does my pod get a new IP each time?** Pod IPs are not stable. Use a [Service](https://www.itwonderlab.com/kubernetes-services-ingress/) and its DNS name.

**What does `latest` do?** It is only a tag. With `:latest`, `imagePullPolicy` defaults to `Always`, and nobody knows what version is running. Pin a version or a digest.

**How do I delete everything?** `kubectl delete -f web.yaml`. Deleting the Deployment deletes its ReplicaSets and pods. `kubectl delete pod` on a managed pod only makes the controller create a new one.

## Next steps

Expose the app with [Services and Ingress](https://www.itwonderlab.com/kubernetes-services-ingress/), move its configuration out of the image with [ConfigMaps and Secrets](https://www.itwonderlab.com/kubernetes-configmaps-secrets/) and keep it healthy with [probes](https://www.itwonderlab.com/kubernetes-probes-resource-limits/).
