Pods, Deployments, and ReplicaSets are some of the first Kubernetes words beginners meet. They are also easy to blur together. All three seem connected to “running my app”, so why does Kubernetes need three objects?

The short version:

  • A Pod is the smallest runnable unit Kubernetes creates for your workload.
  • A ReplicaSet keeps a specified number of matching Pods running.
  • A Deployment manages ReplicaSets so application updates and rollbacks are safer.

That is accurate, but too compressed to be useful on a bad debugging day. The better question is not only what each object is. It is why each object exists, which one you should change, and how to read the chain when something goes wrong.

Start with the Pod

A Pod is Kubernetes’ smallest deployable unit. It usually contains one application container, sometimes with a sidecar container next to it. Containers in the same Pod share a network namespace and can share volumes. They are scheduled together on the same node.

That last sentence matters: Kubernetes does not schedule individual containers. It schedules Pods.

If you run a simple web application, you might imagine “my app runs in a container”. Kubernetes sees “a Pod runs one or more containers”. The Pod gets an IP address, status, events, volumes, environment variables, and container states.

Inspect one:

kubectl get pods -n <namespace> -o wide
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace>

kubectl describe pod is often the best first deep look. It shows image pull problems, scheduling problems, probe failures, restarts, mounted volumes, and events.

But a Pod is also fragile by design. It can be deleted. Its node can fail. Its container can crash. If Kubernetes creates a replacement, that replacement is a new Pod with a new name and usually a new IP address.

That is why beginners should be careful with commands like this:

kubectl run my-app --image=nginx

It is useful for experiments, but it does not teach the production pattern. A manually created Pod has no higher-level controller keeping the intended application shape over time. If it disappears, there may be no object whose job is to bring it back.

Common misconception: “I should edit the Pod to change the app.” Usually not. In real workloads, the Pod is created by a controller. Change the controller’s template, not the individual Pod that happens to be running right now.

Why a ReplicaSet exists

If one Pod is fragile, we need something that keeps enough Pods alive. That is the ReplicaSet’s job.

A ReplicaSet says: for this Pod template and this label selector, make sure there are N matching Pods.

For example, if a ReplicaSet wants three replicas and only two matching Pods exist, it creates one more. If four matching Pods exist, it can remove one. The important part is that the ReplicaSet watches a set of Pods through labels, not through human-friendly names.

You can see the relationship:

kubectl get rs -n <namespace>
kubectl describe rs <replicaset-name> -n <namespace>
kubectl get pods -n <namespace> --show-labels
kubectl get pods -n <namespace> -l app=<label>

This is where label selectors become more than decoration. If the selector does not match the Pod labels, the ReplicaSet will not manage those Pods. If it accidentally matches Pods it should not match, the situation can get confusing quickly.

A ReplicaSet contains a Pod template. That template says what new Pods should look like: image, ports, environment variables, probes, volumes, labels, and so on.

So why not use ReplicaSets directly?

You can, but you normally should not. ReplicaSets maintain a count. They are not a friendly interface for application rollouts. They do not give the same high-level update workflow that Deployments provide.

Why a Deployment exists

A Deployment is the object most beginners should create for a stateless application.

It says: run this application using this Pod template, keep this many replicas, and manage changes over time.

The Deployment controller creates a ReplicaSet. That ReplicaSet creates Pods. When you update the Deployment’s Pod template, Kubernetes creates a new ReplicaSet for the new version and gradually shifts replicas from the old ReplicaSet to the new one.

That is the rollout.

kubectl get deploy,rs,pods -n <namespace>
kubectl rollout status deployment/<name> -n <namespace>
kubectl rollout history deployment/<name> -n <namespace>

The object chain usually looks like this:

Deployment
  -> ReplicaSet
      -> Pod
          -> Container

The Deployment is the source of truth you usually edit. The ReplicaSet is an implementation detail you inspect when debugging. The Pod is the runtime unit you inspect when something crashes or does not become ready.

Common misconception: “Deployment, ReplicaSet, and Pod are three alternatives.” They are not alternatives in the usual case. They are layers. The higher layer owns the lower layer.

A concrete example

Here is a small Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-web
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-web
  template:
    metadata:
      labels:
        app: hello-web
    spec:
      containers:
        - name: web
          image: nginx:1.27
          ports:
            - containerPort: 80

Apply it:

kubectl apply -f hello-web.yaml -n <namespace>
kubectl get deploy,rs,pods -n <namespace>

The Deployment has a selector:

selector:
  matchLabels:
    app: hello-web

The Pod template has matching labels:

template:
  metadata:
    labels:
      app: hello-web

That match is not optional. The Deployment uses it to know which Pods belong to the desired set. Kubernetes requires the selector to match the template labels because otherwise the controller would not know what it owns.

If you later change the image:

kubectl set image deployment/hello-web web=nginx:1.28 -n <namespace>
kubectl rollout status deployment/hello-web -n <namespace>
kubectl get rs -n <namespace>

You should see a new ReplicaSet appear for the new Pod template. The older ReplicaSet may remain with zero replicas. That is useful because rollout history and rollback need a record of previous versions.

Rollback is then a Deployment operation:

kubectl rollout history deployment/hello-web -n <namespace>
kubectl rollout undo deployment/hello-web -n <namespace>

You are not manually rebuilding Pods. You are telling the Deployment controller to move desired state back to a previous template.

What happens when a Pod crashes

Suppose one Pod starts crashing because the application exits. Beginners often ask whether the Deployment restarts it.

The precise answer has layers.

The kubelet on the node restarts containers in a Pod according to the Pod’s restart policy. For normal Deployment Pods, that policy is effectively Always. If the container keeps crashing, you may see CrashLoopBackOff.

If the whole Pod disappears, the ReplicaSet notices that the number of matching Pods is too low and creates another Pod.

The Deployment stays above that. It cares whether the rollout is progressing and whether the desired number of replicas becomes available.

Useful checks:

kubectl get pods -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> --previous
kubectl describe deployment <name> -n <namespace>

Read from the bottom up for runtime problems:

  • Container logs explain application crashes.
  • Pod events explain scheduling, image pull, mount, and probe issues.
  • ReplicaSet status explains whether enough Pods exist.
  • Deployment status explains rollout progress.

Read from the top down for intent:

  • Deployment says what you wanted.
  • ReplicaSet says which Pod template is being maintained.
  • Pods show what is actually running.

Scaling: change the Deployment

If you want more copies of a stateless app, scale the Deployment:

kubectl scale deployment/hello-web --replicas=4 -n <namespace>
kubectl get deploy,rs,pods -n <namespace>

The Deployment updates desired replica count. The ReplicaSet adjusts the Pod count. New Pods are scheduled onto nodes if the cluster has capacity.

Common misconception: “Scaling means making the existing Pod bigger.” Horizontal scaling means more Pods. Vertical sizing is a different question: CPU and memory requests and limits.

If scaling does not work, check events and capacity:

kubectl describe deployment hello-web -n <namespace>
kubectl get events -n <namespace> --sort-by='.lastTimestamp'
kubectl get pods -n <namespace> -o wide
kubectl get nodes

If Pods remain Pending, the problem may be scheduling. If they run but are not Ready, the problem may be probes, application startup, or dependencies.

Updating: change the Pod template

A Deployment rollout happens when the Pod template changes. Changing replicas scales. Changing labels outside the template may not create a new rollout. Changing the image, environment variables, probes, or volume mounts usually changes the template and creates a new ReplicaSet.

This explains a common surprise:

I changed something on the Deployment, but no new Pods appeared.

Ask whether the change affected .spec.template. If it did not, Kubernetes may not need a new ReplicaSet.

You can inspect the Deployment:

kubectl get deployment hello-web -n <namespace> -o yaml
kubectl rollout history deployment/hello-web -n <namespace>
kubectl describe deployment hello-web -n <namespace>

For production changes, I prefer updating manifests in version control and applying them through the team’s normal path. kubectl set image is useful for learning and emergencies, but Git history or Helm values usually make the real source of truth clearer.

Deleting: know which level you mean

Deleting a Pod managed by a ReplicaSet is usually temporary:

kubectl delete pod <pod-name> -n <namespace>

The ReplicaSet notices the missing Pod and creates a replacement.

Deleting the Deployment is different:

kubectl delete deployment hello-web -n <namespace>

By default, Kubernetes also cleans up the ReplicaSets and Pods owned by that Deployment. You removed the desired state at the top, so the lower objects no longer have an owner keeping them alive.

This is why ownership matters so much. Before deleting something, check who owns it:

kubectl get pod <pod-name> -n <namespace> -o yaml | grep -A5 ownerReferences
kubectl get rs <replicaset-name> -n <namespace> -o yaml | grep -A5 ownerReferences

How to think during debugging

When a Deployment-based workload is broken, I like this sequence:

kubectl get deploy,rs,pods -n <namespace>
kubectl describe deployment <name> -n <namespace>
kubectl describe rs <replicaset-name> -n <namespace>
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> --previous
kubectl get events -n <namespace> --sort-by='.lastTimestamp'

Then sort the problem into one of a few buckets:

  • Wrong desired state: bad image, wrong environment variable, missing port, bad selector.
  • Cannot create Pods: quota, admission policy, invalid secret reference, image pull problem.
  • Cannot schedule Pods: no node capacity, taints, affinity, volume constraints.
  • Pods run but are not ready: readiness probe, app startup, dependency failure.
  • Pods crash: application error, missing config, permission problem, bad command.

That classification keeps debugging practical. Without it, it is too easy to stare at one crashing Pod and forget that the real error is a bad Deployment template or a missing Secret.

Final thought

Pods, ReplicaSets, and Deployments are not vocabulary trivia. They are a responsibility chain.

The Pod runs containers. The ReplicaSet keeps the right number of matching Pods alive. The Deployment manages ReplicaSets so updates, rollbacks, and scaling have a stable place to happen.

For beginners, the most useful habit is to ask which layer you are looking at. If you are debugging a crash, inspect the Pod. If you are asking why there are too few Pods, inspect the ReplicaSet and events. If you are changing the application version or replica count, change the Deployment.

Once that layering is clear, Kubernetes feels less like a pile of objects and more like a system with understandable responsibilities.