Pods, Deployments & Services
Kubernetes (often shortened to “K8s” — the “8” stands for the eight letters between the “K” and the “s”) gives you a small set of building blocks for running containers. Three of them do almost all the heavy lifting: Pods (where your containers actually run), Deployments (which keep the right number of Pods alive and roll out new versions), and Services (which give your Pods a stable network address). If you understand these three objects, you understand 80% of day-to-day Kubernetes. This page is the essential vocabulary, with a tiny working YAML example for each.
Pods — the smallest unit
A Pod is the smallest thing Kubernetes can run. It is a wrapper around one or more containers (a container is a packaged, isolated copy of your app and everything it needs to run). Most of the time a Pod holds exactly one container, so you can think of “Pod” and “running instance of my app” as nearly the same thing.
Why not just run a container directly? Because a Pod adds shared extras that containers inside it all use together: a single IP address, shared storage volumes, and a shared lifecycle. Containers in the same Pod can talk to each other over localhost. This is useful for a “sidecar” pattern — for example, your app container plus a small logging container that ships logs out.
Here is a bare Pod definition:
# pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: web
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
Save it and apply it with kubectl (the Kubernetes command-line tool):
kubectl apply -f pod.yaml
kubectl get pods
Output:
pod/web created
NAME READY STATUS RESTARTS AGE
web 1/1 Running 0 7s
When NOT to use a bare Pod: almost always. A Pod created directly is fragile — if the node (the server it runs on) dies, the Pod is gone forever and nothing recreates it. You create bare Pods only for quick one-off debugging. For real workloads, you let a Deployment manage Pods for you.
Labels
Notice the labels: field above (app: web). A label is a simple key/value tag you stick on an object. Labels are how Kubernetes objects find each other — a Service finds its Pods by matching labels, not by name. They are the glue that connects the three objects on this page, so always give your Pods sensible labels.
ReplicaSets — keeping copies alive
A ReplicaSet is the object that guarantees a fixed number of identical Pods are always running. If you ask for 3 copies and one crashes, the ReplicaSet immediately starts a replacement. If a node dies and takes 2 Pods with it, the ReplicaSet recreates them elsewhere.
You almost never create a ReplicaSet by hand. Instead you create a Deployment, which creates and manages ReplicaSets for you. It is worth knowing the name, though, because you will see ReplicaSets appear automatically in kubectl get all.
Deployments — replicas and rollouts
A Deployment is what you actually use to run a stateless app (an app that keeps no important data on its own disk — like a web server or API). It does two big jobs:
- Maintains replicas. You declare “I want 3 copies” and the Deployment (via a ReplicaSet) keeps exactly 3 healthy Pods running at all times.
- Handles rollouts. When you change the container image to a new version, the Deployment rolls it out gradually — starting new Pods, waiting until they are healthy, then removing old ones. If something breaks, you can roll back to the previous version with one command.
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80
The selector.matchLabels tells the Deployment which Pods belong to it, and template is the Pod blueprint it stamps out. Apply it and watch the rollout:
kubectl apply -f deployment.yaml
kubectl rollout status deployment/web
kubectl get pods
Output:
deployment.apps/web created
deployment "web" successfully rolled out
NAME READY STATUS RESTARTS AGE
web-6c9fd7b8d4-7nq2x 1/1 Running 0 12s
web-6c9fd7b8d4-kx4lv 1/1 Running 0 12s
web-6c9fd7b8d4-r8wbt 1/1 Running 0 12s
To ship a new version and roll back if needed:
kubectl set image deployment/web nginx=nginx:1.28
kubectl rollout undo deployment/web # go back to the previous version
Services — stable networking
There is a problem with Pods: they come and go. Every time a Pod restarts it gets a new IP address, so you can never rely on a Pod’s IP to reach your app. A Service solves this by giving you one stable, unchanging address that automatically forwards traffic to whichever Pods are currently healthy. It finds those Pods using a label selector.
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web
spec:
type: ClusterIP
selector:
app: web # matches the Pods' label
ports:
- port: 80 # the Service's port
targetPort: 80 # the container's port
kubectl apply -f service.yaml
kubectl get service web
Output:
service/web created
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.96.142.31 <none> 80/TCP 5s
Now anything inside the cluster can reach all 3 Pods just by using the name web, no matter how often the Pods restart.
Service types — which to use
| Type | What it does | When to use |
|---|---|---|
ClusterIP | Internal-only address inside the cluster | App-to-app traffic (the default and most common) |
NodePort | Opens a fixed port on every node’s IP | Quick testing or bare-metal setups without a load balancer |
LoadBalancer | Asks the cloud (AWS, GCP, Azure) for an external load balancer | Exposing a service to the public internet on a managed cluster |
Gotcha: the Service’s
selectormust match the labels on your Pods exactly. If your Pods sayapp: webbut your Service selectsapp: webapp, the Service will have zero endpoints and silently drop all traffic. Check withkubectl get endpoints web— an empty list means a label mismatch.
How they fit together
The three objects stack neatly: a Service sends traffic to → the Pods that a Deployment keeps running (through a ReplicaSet), and labels are the thread connecting them all.
Service (stable IP) --selects by label--> Pods <--managed by-- ReplicaSet <--owned by-- Deployment
Best Practices
- Never create bare Pods for real workloads — always use a Deployment so failed Pods get recreated automatically.
- Always set meaningful
labels(likeapp:andtier:) and keep the Deployment template labels and Service selector in sync. - Pin your container image to a specific version tag (
nginx:1.27), notlatest, so rollouts are predictable and repeatable. - Use
kubectl rollout statusafter every deploy and keepkubectl rollout undoin your back pocket for fast recovery. - Start every Service as
ClusterIPand only upgrade toLoadBalancerfor the few services that truly need public access. - Run
kubectl get endpoints <service>whenever a Service “isn’t working” — an empty endpoint list almost always means a label selector mismatch.