Right, so you’ve got your app running in a Pod. It’s a beautiful thing. But Pods are mortal; they get sick, they die, they get scheduled onto different nodes. You can’t just kubectl create pod and call it a day. You need a ReplicaSet to keep a desired number of clones running. But even that’s not enough. What happens when you need to update your app from my-app:v1.0 to my-app:v1.1? With a ReplicaSet, your strategy is basically to turn everything off and then turn everything back on again. That’s not an update, that’s an outage. Enter the Deployment. This is where we stop being cowboys and start being engineers.

A Deployment is a higher-level abstraction that manages a ReplicaSet for you. Its superpower is declarative, rolling updates. You tell it the desired state—“I want three replicas of my-app:v1.1"—and it works out how to get there from the current state with zero downtime. It’s the difference between giving a chef a recipe and micromanaging their every chop and stir.

The Anatomy of a Deployment

Let’s look at a simple Deployment manifest. This is your new best friend.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 1
      maxSurge: 1
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.21.6
        ports:
        - containerPort: 80

The key parts here are the strategy and the template. The template is the Pod spec that the managed ReplicaSet will use to create new Pods. The strategy is the brains of the operation. RollingUpdate is the default and what you want 99% of the time. It allows you to fine-tune the rollout process with maxUnavailable and maxSurge.

  • maxUnavailable: The maximum number of Pods that can be unavailable during the update. This can be a number (e.g., 1) or a percentage (e.g., 25%). Setting this to 0 means you want zero downtime, but it also makes the update slower and more conservative.
  • maxSurcre: The maximum number of Pods that can be created over the desired replica count during the update. This allows the rollout to spin up new Pods before killing old ones, speeding things up. A value of 1 means we can have 4 Pods running at once during the update (3 desired + 1 surge).

Apply this, and Kubernetes creates a Deployment, which creates a ReplicaSet, which creates three Pods. Magic.

Performing a Rolling Update

This is the killer feature. To update your application, you simply change the desired state. Edit the manifest file and change the image tag from nginx:1.21.6 to nginx:1.23.4. Then apply it:

kubectl apply -f deployment.yaml

Now watch the orchestration happen. Don’t just take my word for it; run this:

kubectl get pods -l app=nginx -w

You’ll see the Deployment controller slowly, one by one (as per our maxUnavailable: 1), terminate the old Pods and create new ones with the new image. The ReplicaSet for nginx:1.21.6 is scaled down to 0, and a new ReplicaSet for nginx:1.23.4 is created and scaled up to 3. The old ReplicaSet isn’t deleted; it’s kept around for a very good reason.

Rollback: Your “Oh Crap” Button

So you pushed my-app:v2.0, and it’s immediately clear it was a terrible mistake. It’s failing health checks. Users are seeing errors. You need to go back. Now.

This is why the Deployment keeps the old ReplicaSets. To roll back, you use the kubectl rollout command. First, check your rollout history:

kubectl rollout history deployment/nginx-deployment

This will show you a list of revisions. Now, undo the last rollout and go back to the previous version:

kubectl rollout undo deployment/nginx-deployment

Boom. The Deployment will instantly start the rolling update process again, but in reverse. It scales down the new, broken ReplicaSet and scales the previous, stable one back up. Disaster averted in seconds. This is infinitely better than frantically trying to find an old Docker image and manually updating YAML files.

The Dark Arts of Rollout History

You might have noticed the history command is a bit… sparse. By default, it doesn’t show you what changed, just that a change occurred. This is one of those questionable choices I mentioned. To get the full picture, you need to tell Kubernetes to remember the change cause. The easiest way is to always use kubectl apply and set the --record flag:

kubectl apply -f deployment.yaml --record

Now, your history will be annotated with the command that caused the change. It’s a clunky solution, but it works. For a more robust audit trail, you should be storing your manifests in Git and using commit messages—that’s the professional way.

When RollingUpdate Isn’t Enough

There are two other strategy types, but you’ll rarely use them:

  • Recreate: This is the “blow it all up” strategy. It terminates every single Pod in the old ReplicaSet before creating any new ones. There will be downtime. Use this only if your application cannot tolerate two versions running concurrently, which is usually a sign of a different problem.
  • Blue-Green or Canary: These aren’t actual strategy types in the YAML. Kubernetes doesn’t have a built-in primitive for them. You implement them by being clever with Labels, Services, and multiple Deployments. The Deployment’s rolling update is effectively a canary rollout if you set maxUnavailable: 1 and maxSurge: 0—it updates one Pod, waits for it to be healthy, then moves to the next. For more complex patterns, you’ll want to look at a tool like Flagger or Argo Rollouts.

The bottom line? You should almost always be using a Deployment instead of a bare Pod or ReplicaSet. It’s the workhorse that makes your applications reliably updatable and, just as importantly, undoable. It’s the closest thing to actual magic we have in this business.