Kubernetes Deployment: A Founder's Guide to the One File You'd Actually Write (2026)
A Kubernetes Deployment (the API object) explained plainly — rolling updates, replicas, self-healing pods, and why you probably don't need to write one.
Table of contents6 sections
The One File I Actually Wrote That Weekend
I’ve already told you about the 2023 weekend I spun up a real Kubernetes cluster for Clickly — my URL shortener with zero users — and how I tore it down four hours after feeling like a genius. What I didn’t tell you is what that “Hello World” pod actually was under the hood. It wasn’t a pod I created directly. Kubernetes doesn’t really want you doing that. It was a file called deployment.yaml, about twenty lines long, that I copy-pasted from a tutorial, changed one image name in, and ran kubectl apply -f on.
I remember staring at that file afterward, genuinely confused about what I’d just done. I understood the control plane, the nodes, the pods — I’d read enough to sound smart at a dinner party. But this particular object, the Deployment, was the thing I’d actually authored. Everything else in Kubernetes is infrastructure someone else runs for you, even when you self-host it. The Deployment is the one piece of paper with your name on it.
So this post is narrower than the architecture one. That post was the anatomy of the whole ship — control plane, nodes, the works. This one is about the single object you’d hand-write if you ever did run Kubernetes: what it manages, why it exists, and what’s actually happening when you change one line in it and hit enter.
What a Deployment Actually Is
Strip away the ceremony and a Deployment is a description of a desired state for one piece of your app. You write: “I want three copies of this container image, running with this config, exposing this port.” You don’t create Pods directly, and you don’t create ReplicaSets directly either — you write the Deployment, and it creates and owns a ReplicaSet, which in turn creates and owns the actual Pods.
That’s three layers stacked on top of each other, and it trips people up the first time:
- Deployment — the thing you write and edit. Manages history, rollouts, and rollbacks.
- ReplicaSet — the thing the Deployment creates. Its only job is “make sure exactly N pods matching this template exist, right now.”
- Pod — the actual running container(s). Cattle, not pets, exactly like the architecture post said.
Why the extra layer? Because a ReplicaSet is dumb on purpose — it just counts pods and keeps the count right. It has no concept of “history” or “rolling from version 3 to version 4.” The Deployment is the layer that remembers versions. Every time you change the pod template (a new image tag, a new env var, more memory), the Deployment doesn’t edit the existing ReplicaSet in place. It creates a brand new ReplicaSet with the new template, and orchestrates the handoff between the old one and the new one. That handoff is the rolling update, and it’s the entire reason this object exists instead of you just managing ReplicaSets by hand.
Rolling Updates: New Pods Up, Old Pods Down — In That Order
Here’s the part that actually matters for a real product with real traffic. Say you have 4 replicas running version 1 of your API, and you push version 2.
The naive way to deploy would be: kill all 4 old pods, start 4 new ones. Simple, and also a guaranteed outage window — every one of your users hits a dead endpoint for however long it takes the new containers to boot.
A Deployment doesn’t do that by default. Its default strategy is RollingUpdate, and it’s governed by two knobs:
maxSurge— how many extra pods beyond your desired count it’s allowed to create while updating. Default: 25% of your replica count.maxUnavailable— how many of your existing pods it’s allowed to take down at once. Default: 25%.
So with 4 replicas at the defaults, Kubernetes will spin up 1 new pod (25% surge) before touching a single old one. Once that new pod passes its readiness check, it kills one old pod, and repeats — new pod up, confirmed healthy, then one old pod down — until the new ReplicaSet has all 4 and the old one has zero. At no point does your available pod count drop below what your traffic needs, and at no point are you serving requests from a container that isn’t ready yet. New pods up before old ones die. That’s the whole trick, and it’s on by default — you don’t have to configure anything to get it.
There’s a companion setting, minReadySeconds, that defaults to 0 — meaning a pod counts as “ready” the instant its readiness probe passes. Bump that to 10 or 20 if your app needs a warm-up period before it should start taking real traffic; otherwise leave it alone. And if a rollout genuinely gets stuck — a bad image tag, a crash-looping container — the Deployment gives up and marks itself failed after progressDeadlineSeconds, which defaults to 600 seconds (10 minutes). That’s your safety net against a rollout hanging forever with half your fleet on the broken version.
Self-Healing: The Boring Superpower
This is the part that sounds unglamorous and is actually the reason people run Kubernetes at all: if a pod dies, it comes back, and nobody has to page anyone.
This isn’t the Deployment doing something clever in the moment. It’s the reconciliation loop from the architecture post running constantly, in the background, forever. The ReplicaSet says “I want 4 pods matching this template.” The moment one of those pods dies — OOM-killed, node crashed, someone kubectl delete’d it by accident — the actual count drops to 3. The controller-manager notices the gap between desired (4) and actual (3) on its very next pass, which happens continuously, and creates a replacement. No alert has to fire. No human has to SSH into anything. The gap between “what should be true” and “what is true” just gets fixed, quietly, the same way it does when a node itself dies and every pod on it needs a new home.
That’s genuinely the best part of Kubernetes, and it’s not a feature unique to Deployments — it’s the whole platform’s personality. The Deployment just gives that self-healing behavior a version history and a rollout strategy on top.
A Simple Deployment, Annotated
Here’s roughly what that 2023 file of mine looked like, cleaned up:
apiVersion: apps/v1
kind: Deployment
metadata:
name: clickly-api
spec:
replicas: 3
selector:
matchLabels:
app: clickly-api
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: clickly-api
spec:
containers:
- name: clickly-api
image: ghcr.io/shubham/clickly-api:v1
ports:
- containerPort: 3000
readinessProbe:
httpGet:
path: /healthz
port: 3000
A few things worth noticing. replicas: 3 is the desired state I keep mentioning. selector.matchLabels is how the Deployment finds “its” pods — it has to match the labels under template.metadata.labels, or the whole thing silently does nothing. I set maxUnavailable: 0 instead of the 25% default, which means “never drop below 3 healthy pods during a rollout, even if that means surging higher temporarily” — a common override once you actually have traffic you can’t afford to lose. And the readinessProbe is what makes the rolling update honest: without it, Kubernetes considers a pod “ready” the second the container process starts, which for most real apps is well before it can actually serve a request.
You’d deploy that with kubectl apply -f deployment.yaml, watch it with kubectl rollout status deployment/clickly-api, and if v2 turns out to be broken, kubectl rollout undo deployment/clickly-api rolls you back to the last-known-good ReplicaSet — which is exactly why revisionHistoryLimit (default: 10 old ReplicaSets kept around) exists. Scaling is one line too: kubectl scale deployment/clickly-api --replicas=6.
If you’re running more than a couple of these, you stop hand-writing raw YAML and start templating it with Helm — Kubernetes’ package manager, which lets you parameterize a Deployment (image tag, replica count, environment) and reuse the same template across dev, staging, and prod. I never got that far with Clickly. I didn’t need to. Neither, probably, do you.
The Broken Engineer Verdict: You Won’t Write This Either
Here’s the honest part, the same verdict I gave for the cluster itself and for securing it: almost none of this is something you, a solo founder, should be hand-authoring.
Every single behavior I just described — new pods up before old ones die, a dead pod getting silently replaced, a bad rollout getting rolled back — is something Railway, Fly.io, and Koyeb already do for you on every deploy, with zero YAML. When you git push to Railway, it builds your image, starts new instances, waits for them to report healthy, and only then routes traffic away from the old ones. When one of your instances crashes at 3 AM, it gets restarted. You get the Deployment object’s entire feature set — rolling updates, self-healing, effectively maxSurge/maxUnavailable tuned sensibly — without ever typing apiVersion: apps/v1.
The tell, same as always: if you’re asking “how do I write a Deployment,” you almost certainly don’t need to be the one writing it. The people who genuinely hand-craft Deployment manifests are running dozens of services with a platform team standardizing config across all of them — the exact profile I described as the “when it does make sense” case. You are one engineer with a product and a bill to pay. Someone else’s platform has already solved this exact problem, and they solved it years before you needed it solved.
I still have that deployment.yaml sitting in an old repo somewhere, twenty lines of YAML I understood about 30% of the day I wrote it and fully understand now, several failed products later. It taught me something real about how self-healing systems are built — desired state, actual state, a loop that closes the gap, forever. I just didn’t need to run that loop myself. Understand the object. Skip owning it. Go find a user instead.
This is the Broken Engineer Guide. I over-engineer everything, fail at business, and hand you the scars so you can skip them. Stay hungry, keep optimizing — and let someone else write the YAML.
