Kubernetes 101 — Pods, Deployments, and Services Explained
Run your first three Kubernetes objects — Pod, Deployment, Service — on a local cluster, then understand why each one exists and how they fit together.
By the end of this post you'll have a Kubernetes cluster running on your laptop, three core objects deployed to it (Pod, Deployment, Service), and a working mental model for what each one does. About 30 minutes start to finish.
You'll need Docker installed, plus kubectl and a local cluster tool (we'll use kind). Skip ahead to install steps if you don't have them yet.
The mental model
Kubernetes is a system for running container workloads across a fleet of machines. Three core objects show up first when you start using it:
- A Pod is one or more containers running together as a unit. The smallest deployable thing in Kubernetes. Usually one container per Pod.
- A Deployment manages a set of identical Pods. It says "I want N copies of this Pod running at all times." If a Pod dies, the Deployment makes another. If you change the image, it does a rolling update.
- A Service is a stable network endpoint for a set of Pods. Pods come and go (different IPs each time); a Service has one IP that load-balances to whichever Pods are alive.
Almost everything else in Kubernetes (Ingresses, ConfigMaps, StatefulSets, autoscalers, RBAC) is an optimization on top of these three. Master these and you can read most production manifests.
Step 1: Install kubectl and kind
kubectl is the command-line tool that talks to the cluster. kind runs Kubernetes inside Docker so you can have a real cluster on your laptop in under a minute.
On Mac:
brew install kubectl kind
On Linux, follow the official docs for both. On Windows, use WSL2 or Docker Desktop's bundled Kubernetes.
Verify:
kubectl version --client
kind version
You should see version output for both.
Step 2: Start a local cluster
kind create cluster --name k8s-101
kind boots a cluster inside Docker. Takes about 30 seconds. When it finishes:
kubectl get nodes
You should see one node listed with status Ready. That's the whole cluster — one fake "machine" that's really just a Docker container.
Step 3: Run your first Pod
Save this as pod.yaml:
apiVersion: v1
kind: Pod
metadata:
name: hello
spec:
containers:
- name: app
image: nginx:1.27-alpine
ports:
- containerPort: 80
Apply it:
kubectl apply -f pod.yaml
kubectl get pods
You should see hello 1/1 Running 0 30s. The Pod is now running an nginx container.
Test it from inside the cluster:
kubectl exec hello -- curl -s http://localhost:80 | head -5
You should see nginx's default welcome page HTML.
So that's a Pod. One container, running. But Pods don't restart themselves if they die, and you can't easily run multiple copies. That's what Deployments are for.
Step 4: Replace the Pod with a Deployment
kubectl delete pod hello
Save this as deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
spec:
replicas: 3
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: app
image: nginx:1.27-alpine
ports:
- containerPort: 80
Apply it:
kubectl apply -f deployment.yaml
kubectl get pods -l app=hello
You should see three Pods, each with 1/1 Running. The Deployment manages them.
Try killing one:
kubectl delete pod -l app=hello | head -1
kubectl get pods -l app=hello
You'll see the count drop briefly, then the Deployment spins up a replacement. That's the self-healing behavior — you say "I want 3", Kubernetes makes sure there's always 3.
Step 5: Add a Service
The three Pods each have their own IP, but those IPs are ephemeral — they change when Pods restart. A Service gives you one stable endpoint.
Save this as service.yaml:
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
selector:
app: hello
ports:
- port: 80
targetPort: 80
Apply it:
kubectl apply -f service.yaml
kubectl get service hello
You'll see a ClusterIP like 10.96.123.45. That's the stable internal address. Anything in the cluster can hit that IP and reach one of your nginx Pods (load-balanced).
Test it:
kubectl run curl-test --image=curlimages/curl:latest -i --rm --restart=Never -- \
curl -s http://hello.default.svc.cluster.local
You should see nginx HTML returned. The Service did the DNS resolution, picked one of your three Pods, and forwarded the request.
Step 6: Clean up
kind delete cluster --name k8s-101
That deletes the whole cluster. No leftover state on your machine.
Common mistakes
Forgetting the label selector. A Service uses selector to decide which Pods receive traffic. If your Service's selector doesn't match the Pod labels, traffic goes nowhere — silently. Always check kubectl get endpoints <service> to confirm Pods are linked.
Editing a Pod created by a Deployment. If you change a Deployment-managed Pod directly, the Deployment will overwrite your change on the next reconcile. Edit the Deployment, not the Pod.
Confusing Pod and Container. A Pod can have multiple containers (sidecars), but most Pods have one. The Pod is the scheduling unit; the container is what actually runs.
Using kubectl apply without seeing the diff. Run kubectl diff -f <file> before applying — it shows exactly what will change. Saves you from accidental destructive updates.
What to read next
You've used the three core objects. The next levels:
- Kubernetes networking deep dive: Pods, Services, and Ingress — how the packets actually flow once Services and Ingresses are in play
- Kubernetes autoscaling: HPA, VPA, and the cluster autoscaler — when "I want 3 replicas" should turn into "scale based on load"
- GitOps with Argo CD: automating Kubernetes deployments — how teams manage clusters at scale
Pods, Deployments, Services. Three objects, one mental model. Most production manifests are these three plus a handful of namespaces, configmaps, and secrets. Once they click, the rest is incremental.
GitOps Explained — What It Is and Why Teams Adopt It
GitOps in plain words — what it actually is, the workflow it enables, and a hands-on demo using Argo CD on a local Kubernetes cluster.
Linux File Permissions — Read, Write, Execute Without Tears
A clear walkthrough of Linux file permissions. Read the funny rwx- letters, change them safely with chmod, fix "permission denied" errors with confidence.
More from DevOps
Explore more articles in this category
GitHub Actions Workflow Lockfiles: Pin Every Action, Including the Ones You Can't See
GitHub now records the exact commit of every direct and transitive action in .github/workflows/actions.lock. Here is how to adopt it, what it fixes that SHA pinning never did, and where it still leaks.
GitHub Actions macOS 14 Retirement: Fix Your Builds Before Nov 2
GitHub retires the macOS 14 runner image on November 2, and brownouts are already failing jobs. Here is how to find every macos-14 reference and move without breaking Xcode.
PHP2Shell (CVE-2026-109618, CVE-2026-109619): Patch Before the Bots Do
Two unauthenticated RCEs in PHP core, leaked a day before the patch, with exploit attempts already logged. Here is how to tell if you are exposed and what to do tonight.
You might have missed
Evergreen posts worth revisiting.