Skip to content
DevOps devops orchestration 6 min read

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:

  1. Maintains replicas. You declare “I want 3 copies” and the Deployment (via a ReplicaSet) keeps exactly 3 healthy Pods running at all times.
  2. 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

TypeWhat it doesWhen to use
ClusterIPInternal-only address inside the clusterApp-to-app traffic (the default and most common)
NodePortOpens a fixed port on every node’s IPQuick testing or bare-metal setups without a load balancer
LoadBalancerAsks the cloud (AWS, GCP, Azure) for an external load balancerExposing a service to the public internet on a managed cluster

Gotcha: the Service’s selector must match the labels on your Pods exactly. If your Pods say app: web but your Service selects app: webapp, the Service will have zero endpoints and silently drop all traffic. Check with kubectl 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 (like app: and tier:) and keep the Deployment template labels and Service selector in sync.
  • Pin your container image to a specific version tag (nginx:1.27), not latest, so rollouts are predictable and repeatable.
  • Use kubectl rollout status after every deploy and keep kubectl rollout undo in your back pocket for fast recovery.
  • Start every Service as ClusterIP and only upgrade to LoadBalancer for 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.
Last updated June 15, 2026
Was this helpful?