Kubernetes Entry Created: 05 Mar 2026 Updated: 05 Mar 2026

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:

  1. Delete all Pods and recreate them. Simple, but causes downtime — there is a window where zero Pods are running.
  2. 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:

ObjectResponsibility
DeploymentManages rollouts, version history, and update strategy. Owns one or more ReplicaSets.
ReplicaSetEnsures a specific number of identical Pods are always running. Owned by a Deployment.
PodRuns 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:

apiVersion: apps/v1
kind: Deployment
metadata:
name: order-api
labels:
app: order-api
spec:
replicas: 3
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
minReadySeconds: 30
selector:
matchLabels:
app: order-api
env: prod
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
template:
metadata:
labels:
app: order-api
env: prod

The key fields in addition to the familiar replicas, selector, and template are:

FieldPurpose
strategy.typeRollingUpdate (default) or Recreate.
strategy.rollingUpdate.maxUnavailableMaximum Pods that may be unavailable during the update (absolute or %).
strategy.rollingUpdate.maxSurgeMaximum extra Pods that may be created above the desired count during the update.
minReadySecondsHow long a newly-ready Pod must stay healthy before the next Pod is updated.
progressDeadlineSecondsTimeout for any single progress step; marks the rollout failed if exceeded.
revisionHistoryLimitHow many old ReplicaSets (revisions) to keep for rollback purposes.

Step 4: Creating and Inspecting a Deployment

Create a Deployment from a manifest file:

kubectl apply -f order-api-deployment.yaml

List all Deployments in the namespace:

kubectl get deployments

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:

kubectl describe deployment order-api

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:

kubectl get replicasets --selector=app=order-api,env=prod

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:

kubectl apply -f order-api-deployment.yaml

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:

kubectl scale deployment order-api --replicas=6

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):

spec:
template:
metadata:
annotations:
kubernetes.io/change-cause: "Release v2 — improved order processing"
spec:
containers:
- name: order-api
image: mcr.microsoft.com/dotnet/aspnet:10.0
# bump your private registry tag here, e.g. order-api:2.0

Apply the update. Kubernetes immediately starts the rollout:

kubectl apply -f order-api-deployment.yaml

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):

kubectl rollout status deployment order-api

Expected success output:

deployment "order-api" successfully rolled out

If the rollout is taking too long, check events and Pod status:

kubectl describe deployment order-api
kubectl get pods -l app=order-api,env=prod

To see both the old and new ReplicaSets side-by-side during a rollout:

kubectl get replicasets -o wide --selector=app=order-api

Step 8: Rollout History and Revisions

Every applied change to the Pod template creates a new revision. View the full history:

kubectl rollout history deployment order-api

Example output:

REVISION CHANGE-CAUSE
1 Initial deployment v1
2 Release v2 — improved order processing
3 Release v3 — hotfix for order calculation

Inspect what changed in a specific revision:

kubectl rollout history deployment order-api --revision=2

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:

spec:
revisionHistoryLimit: 14

Step 9: Rolling Back

Roll back to the immediately previous revision (the most common case after a bad release):

kubectl rollout undo deployment order-api

Roll back to a specific historical revision:

kubectl rollout undo deployment order-api --to-revision=2

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:

kubectl rollout pause deployment order-api

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:

kubectl rollout resume deployment order-api

…or roll back:

kubectl rollout undo deployment order-api

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.

strategy:
type: Recreate

Use Recreate only for:

  1. Development or test environments where downtime is acceptable.
  2. 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:

ParameterWhat it limitsTrade-off
maxUnavailableMax Pods that can be down at once (absolute or %)Higher → faster rollout, lower capacity during update
maxSurgeMax extra Pods above desired count (absolute or %)Higher → faster rollout, more resource cost during update

Common configurations:

  1. Default (25% / 25%): Balanced — allows up to 25% capacity drop and 25% temporary overprovisioning. Good for most services.
  2. 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.
  3. 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.
  4. 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:

spec:
minReadySeconds: 60

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:

spec:
progressDeadlineSeconds: 600

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:

kubectl get deployment order-api \
-o=jsonpath='{.status.conditions[?(@.type=="Progressing")].status}'

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:

kubectl delete deployment order-api

Or using the manifest file:

kubectl delete -f order-api-deployment.yaml

To delete only the Deployment object and leave the ReplicaSets and Pods running (for example, when migrating to a new Deployment definition without downtime):

kubectl delete deployment order-api --cascade=false

Hands-On: Kubernetes Commands

Create or update a Deployment from a manifest file:

kubectl apply -f order-api-deployment.yaml

List all Deployments in the namespace:

kubectl get deployments

Describe a Deployment (labels, replicas, strategy, events, ReplicaSet references):

kubectl describe deployment order-api

List ReplicaSets owned by a Deployment:

kubectl get replicasets --selector=app=order-api

Watch rollout progress (blocks until complete or failed):

kubectl rollout status deployment order-api

View rollout history:

kubectl rollout history deployment order-api

Inspect a specific revision:

kubectl rollout history deployment order-api --revision=2

Roll back to the previous revision:

kubectl rollout undo deployment order-api

Roll back to a specific revision:

kubectl rollout undo deployment order-api --to-revision=2

Pause a rollout:

kubectl rollout pause deployment order-api

Resume a paused rollout:

kubectl rollout resume deployment order-api

Scale imperative (emergency use only — update the manifest afterwards):

kubectl scale deployment order-api --replicas=6

Check the Progressing condition (useful in CI/CD pipelines):

kubectl get deployment order-api \
-o=jsonpath='{.status.conditions[?(@.type=="Progressing")].status}'

Delete a Deployment and all its Pods:

kubectl delete deployment order-api

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:

FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src
COPY . .
RUN dotnet publish -c Release -o /app

FROM mcr.microsoft.com/dotnet/aspnet:10.0
WORKDIR /app
COPY --from=build /app .
ENTRYPOINT ["dotnet", "OrderApi.dll"]

Register health check endpoints in Program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHealthChecks();

var app = builder.Build();
app.MapHealthChecks("/healthz");
app.MapHealthChecks("/health/ready");
app.MapControllers();
app.Run();

Step 1: Deploy Version 1

Apply the manifest (see order-api-deployment.yaml in this folder):

kubectl apply -f order-api-deployment.yaml

Wait for the rollout to complete and verify all three replicas are ready:

kubectl rollout status deployment order-api
kubectl get pods -l app=order-api,env=prod

Expected output:

NAME READY STATUS RESTARTS AGE
order-api-74d5975bc4-6gkqp 1/1 Running 0 45s
order-api-74d5975bc4-m9txz 1/1 Running 0 45s
order-api-74d5975bc4-sxr2f 1/1 Running 0 45s

Step 2: Inspect the Deployment

kubectl describe deployment order-api

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:

kubectl get replicasets --selector=app=order-api -o wide

Step 3: Scale Declaratively to Five Replicas

Edit order-api-deployment.yaml: change spec.replicas from 3 to 5 and apply:

kubectl apply -f order-api-deployment.yaml
kubectl get deployment order-api

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:

template:
metadata:
annotations:
kubernetes.io/change-cause: "Release v2 — async order processing"
spec:
containers:
- name: order-api
image: <your-registry>/order-api:2.0

Apply and monitor:

kubectl apply -f order-api-deployment.yaml
kubectl rollout status deployment order-api

While the rollout is active, watch two ReplicaSets (old and new) side-by-side:

kubectl get replicasets --selector=app=order-api -o wide

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

kubectl rollout history deployment order-api

Expected output:

REVISION CHANGE-CAUSE
1 Initial deployment v1
2 Release v2 — async order processing

Inspect revision 1 in detail:

kubectl rollout history deployment order-api --revision=1

Step 6: Roll Back to Version 1

Simulate a production incident — roll back immediately:

kubectl rollout undo deployment order-api

Confirm the rollback succeeded:

kubectl rollout status deployment order-api
kubectl rollout history deployment order-api

History now shows revision 1 renumbered as revision 3 (the template was reused and assigned the next available number). Revision 2 still exists:

REVISION CHANGE-CAUSE
2 Release v2 — async order processing
3 Initial deployment v1

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:

kubectl set image deployment/order-api order-api=<your-registry>/order-api:does-not-exist

Watch Kubernetes report the failure:

kubectl rollout status deployment order-api

After progressDeadlineSeconds (600 s by default), the Deployment is marked Failed. Roll back immediately without waiting for the deadline:

kubectl rollout undo deployment order-api

Step 8: Clean Up

kubectl delete deployment order-api

Verify that the Pods and ReplicaSets are also gone:

kubectl get pods -l app=order-api,env=prod
kubectl get replicasets --selector=app=order-api

Summary

  1. 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.
  2. 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.
  3. 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.
  4. Tune maxUnavailable and maxSurge to balance rollout speed against service capacity. Setting maxUnavailable: 0 + maxSurge: 100% implements a blue/green rollout.
  5. Use minReadySeconds to add a soak period after a Pod becomes ready, and progressDeadlineSeconds to time out stuck rollouts. In automated pipelines, a timed-out Deployment should trigger an alert or automatic rollback.
  6. kubectl rollout history shows all revisions. kubectl rollout undo rolls 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.
  7. Set revisionHistoryLimit to a finite number (e.g., 14 for two weeks) to prevent accumulated ReplicaSets from cluttering the namespace over time.
  8. Use kubectl rollout pause to freeze a rollout mid-flight for investigation without causing a full rollback or service disruption.
Share this lesson: