Creating Kubernetes Deployments
Write, apply, inspect and scale a Deployment: the required fields, the immutable selector, how ReplicaSets relate to it, and the errors you meet first.
A Deployment declares how many identical Pods should exist and what they should look like. A controller then makes that true and keeps it true.
The quickest version
kubectl create deployment web --image=nginx:1.27 --replicas=3
kubectl get deployments
NAME READY UP-TO-DATE AVAILABLE AGE
web 3/3 3 3 8s
That is fine for experimenting and wrong for anything you intend to keep. Nothing records why the cluster is in this state, and nobody can reproduce it.
Use the imperative command to generate a manifest instead:
kubectl create deployment web --image=nginx:1.27 --replicas=3 \
--dry-run=client -o yaml > deployment.yaml
Nothing is sent to the cluster; you get a valid skeleton to edit and commit.
The manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: payments-api
labels:
app: payments-api
spec:
replicas: 3
selector:
matchLabels:
app: payments-api # must match template labels below
template:
metadata:
labels:
app: payments-api # must match selector above
spec:
containers:
- name: api
image: ghcr.io/acme/payments-api:1.4.2
ports:
- containerPort: 8080
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 512Mi
readinessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5
kubectl apply -f deployment.yaml
Four things in that file deserve a moment.
selector.matchLabels must equal template.metadata.labels. The API server rejects a mismatch, which is helpful. The selector is how the Deployment identifies the Pods it owns.
The selector is immutable. Changing it after creation requires deleting and recreating the Deployment, so pick labels you can live with. app.kubernetes.io/name and app.kubernetes.io/instance are the recommended conventions if you want something more structured than app.
Pin the image tag. :1.4.2, never :latest. A mutable tag means two Pods from one manifest can run different code, and a rollback has nothing to roll back to.
Set resource requests. Without them the scheduler assumes the Pod needs nothing, places it badly, and marks it BestEffort so it is evicted first under pressure.
What it actually created
kubectl get deployment,replicaset,pods -l app=payments-api
NAME READY UP-TO-DATE AVAILABLE
deployment.apps/payments-api 3/3 3 3
NAME DESIRED CURRENT READY
replicaset.apps/payments-api-7d4f9c8b5 3 3 3
NAME READY STATUS RESTARTS AGE
pod/payments-api-7d4f9c8b5-2xk9p 1/1 Running 0 22s
pod/payments-api-7d4f9c8b5-7mq4t 1/1 Running 0 22s
pod/payments-api-7d4f9c8b5-k3n8w 1/1 Running 0 22s
Three objects, one command. The Deployment created a ReplicaSet, the ReplicaSet created the Pods, and the Pod names carry both the ReplicaSet hash and a random suffix.
That hash is derived from the pod template. Change the template and you get a new ReplicaSet with a new hash, which is the mechanism behind rolling updates and rollbacks.
The columns are worth reading precisely:
| Column | Meaning |
|---|---|
READY | Pods passing readiness / desired |
UP-TO-DATE | Pods running the current template |
AVAILABLE | Ready for at least minReadySeconds |
A Deployment showing 3/3 ready but 1 up-to-date is mid-rollout.
Scaling
kubectl scale deployment/payments-api --replicas=5
This changes the ReplicaSet in place, scaling does not create a new revision, because the pod template has not changed.
If you scale imperatively, remember the manifest still says 3, so the next kubectl apply reverts it. Either change the file or, if load varies, use a Horizontal Pod Autoscaler and remove replicas from the manifest altogether so the two do not fight.
Inspecting
kubectl describe deployment payments-api # spec, conditions, events
kubectl get deployment payments-api -o yaml # full object as stored
kubectl rollout status deployment/payments-api
kubectl describe shows conditions that answer most questions directly:
Conditions:
Type Status Reason
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
Available: False means not enough Pods are ready. Progressing: False with reason ProgressDeadlineExceeded means a rollout gave up.
The errors you will hit
selector does not match template labels: the two label sets disagree. Make them identical.
field is immutable on spec.selector. You changed the selector on an existing Deployment. Delete and recreate it.
Pods Pending: nothing can schedule them. kubectl describe pod names the reason: insufficient CPU or memory, an untolerated taint, or an unbound volume claim.
Pods ImagePullBackOff: wrong image name or tag, or a private registry with no imagePullSecrets in this namespace.
0/3 ready and staying that way: the containers start but fail readiness. Check the probe path and port, and give slow-starting applications a startup probe rather than a longer initialDelaySeconds.
Deleting
kubectl delete deployment payments-api
# or, preferably
kubectl delete -f deployment.yaml
Deleting a Deployment cascades to its ReplicaSets and Pods through owner references. Deleting the ReplicaSet directly is a mistake. The Deployment simply recreates it.
Quick reference
| Task | Command |
|---|---|
| Generate a manifest | kubectl create deployment <n> --image=<i> --dry-run=client -o yaml |
| Apply | kubectl apply -f deployment.yaml |
| Status | kubectl get deployments |
| Diagnose | kubectl describe deployment <n> |
| See the ReplicaSets | kubectl get rs -l app=<label> |
| Scale | kubectl scale deployment/<n> --replicas=5 |
| Watch a rollout | kubectl rollout status deployment/<n> |
| Restart all Pods | kubectl rollout restart deployment/<n> |
| Delete | kubectl delete -f deployment.yaml |
Key takeaways
- Generate manifests with
--dry-run=client -o yaml, then commit them. Do not manage real workloads imperatively. selector.matchLabelsmust equaltemplate.metadata.labels, and the selector is immutable afterwards.- A Deployment creates a ReplicaSet, which creates the Pods. The hash in Pod names comes from the template.
- Scaling changes the ReplicaSet in place and creates no new revision.
- Pin image tags and set resource requests. Both are one line and both prevent a class of incident.
READY,UP-TO-DATEandAVAILABLEmean different things: read all three during a rollout.- Delete the Deployment, never the ReplicaSet.
Check your understanding
Changing the selector
You edit an existing Deployment to change selector.matchLabels from app: api to app.kubernetes.io/name: api, and update the template labels to match. kubectl apply fails. Why?
A scale that did not stick
You run kubectl scale deployment/api --replicas=10 during a traffic spike. The next CI deploy runs kubectl apply -f deployment.yaml and the count drops to 3. Why?
Frequently Asked Questions
How do I create a Kubernetes Deployment?
Write a manifest with apiVersion: apps/v1, kind: Deployment, a replica count, a selector, and a pod template, then run kubectl apply -f deployment.yaml. The fastest way to get a correct starting point is kubectl create deployment web --image=nginx:1.27 --dry-run=client -o yaml > deployment.yaml, which generates valid YAML without contacting the cluster. Edit that, commit it, and apply it. Managing real workloads through imperative commands leaves no record of why the cluster looks the way it does.
Why does my Deployment fail with "selector does not match template labels"?
spec.selector.matchLabels and spec.template.metadata.labels must contain the same key-value pairs. The selector is how the Deployment recognises the Pods it owns, so a mismatch would mean it creates Pods it cannot then manage, and the API server rejects it. Make the two blocks identical. Note that the selector is immutable after creation, so if you need a different label scheme later you must delete and recreate the Deployment.
What is the difference between a Deployment and a ReplicaSet?
A ReplicaSet keeps a fixed number of identical Pods running and does nothing else. A Deployment manages ReplicaSets and adds version handling: changing the pod template creates a new ReplicaSet, shifts replicas from the old one to the new gradually, and retains previous ReplicaSets at zero replicas so you can roll back. You create Deployments; the ReplicaSets are an implementation detail you mostly read rather than write.
Why are my Deployment's Pods stuck at 0/3 ready?
The containers are running but failing their readiness probe, so they never become Ready. Run kubectl describe pod <name> and look at the events for the probe's failure message. The usual causes are a wrong path or port, an endpoint behind authentication returning 401, or an initialDelaySeconds shorter than the application's genuine startup time. For slow-starting applications, add a startup probe rather than inflating initialDelaySeconds, which would otherwise delay detection of real failures later.
How do I scale a Deployment?
kubectl scale deployment/<name> --replicas=5 changes it immediately, and editing replicas in the manifest and reapplying does the same declaratively. Remember that imperative scaling is undone by the next kubectl apply if the file still specifies a different number. Where load varies, a HorizontalPodAutoscaler is the better answer, and when you adopt one, remove replicas from the manifest entirely so the autoscaler and your deployment tooling are not writing the same field.
What do READY, UP-TO-DATE and AVAILABLE mean?
READY shows how many Pods are passing readiness checks against how many are desired. UP-TO-DATE counts Pods running the current pod template, which is how you see rollout progress. AVAILABLE counts Pods that have been ready for at least minReadySeconds, so it lags READY slightly when that is configured. During a rollout these diverge, 3/3 ready with only 1 up-to-date means the new version is still being rolled out.