Kubernetes Deployments
Overview
Pods and ReplicaSets are excellent building blocks for running your application, but they solve a static problem: "run N copies of this container." They do nothing to help you answer the dynamic question every engineering team faces every week: "how do I safely ship a new version without downtime?"
The Deployment object was created specifically to answer that question. A Deployment represents your application across time — through every version you will ever release. It manages a rollout process that replaces old Pods with new ones incrementally, checks health at each step, and stops automatically if something goes wrong. Deployments also keep a revision history so you can roll back to any previous version with a single command.
Under the hood, a Deployment works by managing one or more ReplicaSets. For every new version you deploy, Kubernetes creates a new ReplicaSet. The old ReplicaSet is scaled down as the new one is scaled up. The Deployment controller runs entirely server-side, which means a rollout continues safely even if your laptop disconnects mid-update.
Core Concepts
Step 1: Why Deployments Exist
Imagine you have a microservice running at version 1.0. You want to release version 1.1. Without Deployments you have two options, both bad:
- Delete all Pods and recreate them. Simple, but causes downtime — there is a window where zero Pods are running.
- Create new Pods first, then delete old ones. No downtime, but you have to orchestrate this by hand, track which Pods are old, watch health checks, and handle failures manually. Error-prone and time-consuming.
A Deployment automates the second approach. It handles the orchestration, health checking, and failure handling for you — consistently, every time.
Step 2: The Deployment → ReplicaSet → Pod Hierarchy
Kubernetes workload objects form a three-level hierarchy:
| Object | Responsibility |
|---|---|
| Deployment | Manages rollouts, version history, and update strategy. Owns one or more ReplicaSets. |
| ReplicaSet | Ensures a specific number of identical Pods are always running. Owned by a Deployment. |
| Pod | Runs the actual containers. Owned by a ReplicaSet. |
When you scale a Deployment, it updates the desired replica count on its current ReplicaSet. When you update a container image, the Deployment creates a new ReplicaSet for the new version and gradually shifts traffic from the old ReplicaSet to the new one. The old ReplicaSet is kept at zero replicas (not deleted) so that rollbacks remain possible.
An important consequence: if you try to scale a Deployment's ReplicaSet directly (bypassing the Deployment), the Deployment controller will immediately undo your change to restore the desired state it manages. Always interact with the Deployment, never with the underlying ReplicaSet.
Step 3: The Deployment Spec
A Deployment spec looks nearly identical to a ReplicaSet spec, with one key addition — the strategy field:
The key fields in addition to the familiar replicas, selector, and template are:
| Field | Purpose |
|---|---|
strategy.type | RollingUpdate (default) or Recreate. |
strategy.rollingUpdate.maxUnavailable | Maximum Pods that may be unavailable during the update (absolute or %). |
strategy.rollingUpdate.maxSurge | Maximum extra Pods that may be created above the desired count during the update. |
minReadySeconds | How long a newly-ready Pod must stay healthy before the next Pod is updated. |
progressDeadlineSeconds | Timeout for any single progress step; marks the rollout failed if exceeded. |
revisionHistoryLimit | How many old ReplicaSets (revisions) to keep for rollback purposes. |
Step 4: Creating and Inspecting a Deployment
Create a Deployment from a manifest file:
List all Deployments in the namespace:
The output columns are: READY (running/desired), UP-TO-DATE (Pods running the newest template), and AVAILABLE (Pods passing readiness checks).
Get a detailed description including label selector, rollout strategy, and recent events:
Pay attention to the OldReplicaSets and NewReplicaSet fields. During an active rollout, both show a value. When the rollout finishes, OldReplicaSets becomes <none>.
Find the ReplicaSet the Deployment is currently managing:
Step 5: Scaling a Deployment
The preferred approach is declarative: edit spec.replicas in your manifest file, commit the change to source control, and apply:
For emergencies you can scale imperatively, but always follow up by updating the manifest file to match — otherwise the next kubectl apply will silently revert your change:
Step 6: Updating a Container Image
To roll out a new version, update the container image tag in your manifest and add a change-cause annotation to the Pod template (not to the Deployment itself):
Apply the update. Kubernetes immediately starts the rollout:
Important: Do not change the change-cause annotation when you are only scaling. Changing the template annotation counts as a template modification and triggers a new rollout, which is not what you want for a pure scale operation.
Step 7: Monitoring a Rollout
Watch the real-time progress of the current rollout. The command blocks until the rollout completes (or fails):
Expected success output:
If the rollout is taking too long, check events and Pod status:
To see both the old and new ReplicaSets side-by-side during a rollout:
Step 8: Rollout History and Revisions
Every applied change to the Pod template creates a new revision. View the full history:
Example output:
Inspect what changed in a specific revision:
By default Kubernetes keeps the last 10 revisions. Tune this with spec.revisionHistoryLimit. For a service that ships daily, keeping 14 revisions gives you two weeks of rollback history:
Step 9: Rolling Back
Roll back to the immediately previous revision (the most common case after a bad release):
Roll back to a specific historical revision:
An undo is itself a rollout — it obeys the same maxUnavailable and maxSurge settings as a forward rollout. After the undo, the reverted template is renumbered as the latest revision (the original revision number disappears from history).
Best practice: A kubectl rollout undo updates the live cluster without updating your YAML files in source control. Prefer reverting the manifest in git and applying the old version declaratively so your repository always reflects what is running in production.
Step 10: Pausing and Resuming a Rollout
If you notice unexpected behaviour mid-rollout (for example, error rates rising), pause immediately without rolling back:
A paused rollout stops creating new Pods and deleting old ones. Existing Pods keep running, so your service continues to operate at partial capacity. Once you have diagnosed the issue, either resume the rollout:
…or roll back:
Step 11: The Recreate Strategy
The Recreate strategy is the simplest possible update approach: Kubernetes terminates all existing Pods first, then creates all new Pods. There is a brief window where zero Pods are running.
Use Recreate only for:
- Development or test environments where downtime is acceptable.
- Applications that cannot run two versions simultaneously (e.g., database schema migrations that are not backward-compatible).
Never use Recreate for production user-facing services.
Step 12: The RollingUpdate Strategy
RollingUpdate replaces Pods incrementally: a few Pods are replaced at a time until all Pods run the new version. During the transition, both versions are live simultaneously. This has a critical implication: your application must handle requests from clients running against either the old or the new API version at any moment during the rollout. Design your APIs to be backward-compatible and forward-compatible — this is not optional.
Two parameters control how fast the rollout proceeds and how much capacity you maintain:
| Parameter | What it limits | Trade-off |
|---|---|---|
maxUnavailable | Max Pods that can be down at once (absolute or %) | Higher → faster rollout, lower capacity during update |
maxSurge | Max extra Pods above desired count (absolute or %) | Higher → faster rollout, more resource cost during update |
Common configurations:
- Default (25% / 25%): Balanced — allows up to 25% capacity drop and 25% temporary overprovisioning. Good for most services.
- Zero-downtime (
maxUnavailable: 0,maxSurge: 1): Guarantees 100% capacity at all times. One extra Pod is created before any old Pod is removed. Slower and requires slightly more resources momentarily. - Blue/green (
maxUnavailable: 0,maxSurge: 100%): The entire new version is created alongside the old version. Once all new Pods are healthy, all old Pods are removed at once. Requires double the resources briefly. Provides the cleanest cutover. - Fast (equivalent to Recreate):
maxUnavailable: 100%: All old Pods removed before any new Pods are created. Causes downtime.
Step 13: Slowing Rollouts — minReadySeconds and progressDeadlineSeconds
The Deployment controller waits until each new Pod passes its readiness probe before moving on. But readiness alone is not always sufficient — some bugs only manifest under real load after a few minutes. Use minReadySeconds to add a soak period:
With this setting, the controller waits an additional 60 seconds after a Pod becomes ready before it proceeds to replace the next Pod. This gives you time to catch slow-burn failures like memory leaks or race conditions.
To prevent a broken rollout from hanging indefinitely, set a progress deadline:
If no progress (Pod creation or deletion) occurs within 600 seconds, the Deployment is marked failed. In automated CI/CD pipelines this failed status should trigger an alert or an automatic rollback, rather than waiting for a human to notice.
Check the failure condition programmatically:
A value of False means the Deployment has timed out.
Step 14: Deleting a Deployment
Delete a Deployment and all its managed ReplicaSets and Pods:
Or using the manifest file:
To delete only the Deployment object and leave the ReplicaSets and Pods running (for example, when migrating to a new Deployment definition without downtime):
Hands-On: Kubernetes Commands
Create or update a Deployment from a manifest file:
List all Deployments in the namespace:
Describe a Deployment (labels, replicas, strategy, events, ReplicaSet references):
List ReplicaSets owned by a Deployment:
Watch rollout progress (blocks until complete or failed):
View rollout history:
Inspect a specific revision:
Roll back to the previous revision:
Roll back to a specific revision:
Pause a rollout:
Resume a paused rollout:
Scale imperative (emergency use only — update the manifest afterwards):
Check the Progressing condition (useful in CI/CD pipelines):
Delete a Deployment and all its Pods:
Step-by-Step Example
In this example you will deploy version 1 of an ASP.NET Core 10 Order API, inspect the Deployment, update to a new image tag, watch the rollout, roll back, and configure HPA.
Application: Order API
Build the Order API image with this Dockerfile:
Register health check endpoints in Program.cs:
Step 1: Deploy Version 1
Apply the manifest (see order-api-deployment.yaml in this folder):
Wait for the rollout to complete and verify all three replicas are ready:
Expected output:
Step 2: Inspect the Deployment
Note the RollingUpdateStrategy line, the NewReplicaSet field, that OldReplicaSets is <none> (rollout is complete), and the Events section showing the initial scaling action.
Confirm the Deployment owns one ReplicaSet:
Step 3: Scale Declaratively to Five Replicas
Edit order-api-deployment.yaml: change spec.replicas from 3 to 5 and apply:
The same ReplicaSet now has 5 desired replicas. No new ReplicaSet is created — scaling never creates a new revision.
Step 4: Roll Out Version 2
Update the image tag in order-api-deployment.yaml to point to your v2 image and update the change-cause annotation on the Pod template:
Apply and monitor:
While the rollout is active, watch two ReplicaSets (old and new) side-by-side:
You will see the old ReplicaSet scaling down as the new one scales up. The Deployment's maxSurge: 25% allows one extra Pod (25% of 5 ≈ 1), and the maxUnavailable: 25% allows one Pod to be replaced at a time. The minReadySeconds: 30 ensures each new Pod is healthy for 30 seconds before the next replacement begins.
Step 5: View Rollout History
Expected output:
Inspect revision 1 in detail:
Step 6: Roll Back to Version 1
Simulate a production incident — roll back immediately:
Confirm the rollback succeeded:
History now shows revision 1 renumbered as revision 3 (the template was reused and assigned the next available number). Revision 2 still exists:
Step 7: Simulate a Stuck Rollout
Update the image to a non-existent tag (an invalid image to simulate a broken release). The new Pods will fail to pull the image and the rollout will stall:
Watch Kubernetes report the failure:
After progressDeadlineSeconds (600 s by default), the Deployment is marked Failed. Roll back immediately without waiting for the deadline:
Step 8: Clean Up
Verify that the Pods and ReplicaSets are also gone:
Summary
- A Deployment manages the full lifecycle of your application across versions. It adds rollout, rollback, history, and health-gated update logic on top of a ReplicaSet.
- A Deployment manages ReplicaSets, not Pods directly. Each new image version gets its own ReplicaSet. Scaling the ReplicaSet directly is an antipattern — always act on the Deployment.
- The two update strategies are Recreate (simple, causes downtime) and RollingUpdate (incremental, zero-downtime but requires API backward compatibility). Use RollingUpdate for all production services.
- Tune
maxUnavailableandmaxSurgeto balance rollout speed against service capacity. SettingmaxUnavailable: 0+maxSurge: 100%implements a blue/green rollout. - Use
minReadySecondsto add a soak period after a Pod becomes ready, andprogressDeadlineSecondsto time out stuck rollouts. In automated pipelines, a timed-out Deployment should trigger an alert or automatic rollback. kubectl rollout historyshows all revisions.kubectl rollout undorolls back to the previous revision. Always follow a rollback by reverting the manifest in source control so your repository stays in sync with the cluster.- Set
revisionHistoryLimitto a finite number (e.g., 14 for two weeks) to prevent accumulated ReplicaSets from cluttering the namespace over time. - Use
kubectl rollout pauseto freeze a rollout mid-flight for investigation without causing a full rollback or service disruption.