on
Pods, Deployments, and Services: How Kubernetes Runs and Exposes Your App
Kubernetes can feel overwhelming at first, but three core concepts — Pods, Deployments, and Services — are enough to run and expose most apps. This article explains what each piece does, how they work together, and gives small, copy-ready examples you can read and learn from.
Quick mental model
- Pod: the smallest unit that actually runs your containers (think: one or more tightly-coupled containers sharing network and storage).
- Deployment: the controller that ensures a desired number of Pods are running, and that manages updates and scaling.
- Service: a stable network endpoint that lets other parts of the cluster (or the outside world) reach the Pods managed by a Deployment.
Each of these concepts maps to a Kubernetes object you create with YAML or kubectl — together they let Kubernetes run apps reliably and expose them in a predictable way.
Pods: the runtime unit
A Pod represents one or more containers that run together on the same node and share an IP address, network namespace, and optional volumes. For most beginner use cases you’ll see one container per Pod — Kubernetes treats the Pod as the unit it schedules and manages. Pods are intentionally ephemeral: when a Pod dies, Kubernetes can create a new Pod (usually via a higher-level controller like a Deployment), but the new Pod is a distinct object with a different IP. (kubernetes.io)
Key Pod properties to notice:
- Shared network: containers in the same Pod can use localhost to talk to each other.
- Ephemeral lifecycle: Pods can stop and be replaced; persistent state needs persistent volumes.
- Labels: Pods commonly get labels (key/value pairs) that other objects use to select them.
A very small Pod manifest (single-container) looks like this:
apiVersion: v1
kind: Pod
metadata:
name: hello-pod
labels:
app: hello
spec:
containers:
- name: web
image: nginx:stable
ports:
- containerPort: 80
You normally don’t create standalone Pods for production services; you create a Deployment so Kubernetes can maintain the desired replica count.
Deployments: desired state and safe updates
A Deployment declares the desired state for a set of Pods (how many replicas, what image to run, etc.) and takes care of creating ReplicaSets and Pods to match that state. You tell the Deployment “run 3 replicas of this container image,” and Kubernetes keeps them running. Deployments also enable controlled rollouts: when you update the Pod template (for example, change the container image), Kubernetes performs a rolling update so your service stays available. The rolling-update behavior is the standard update strategy for Deployments. (kubernetes.io)
A basic Deployment manifest:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello-deployment
spec:
replicas: 3
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: web
image: gcr.io/google-samples/echoserver:1.10
ports:
- containerPort: 8080
Common kubectl commands you’ll use with a Deployment:
- kubectl apply -f deployment.yaml # create or update the Deployment
- kubectl get deployments # list Deployments
- kubectl get pods # see the Pods created by the Deployment
- kubectl rollout status deployment/hello-deployment # watch a rollout
Because a Deployment controls ReplicaSets and Pods, you don’t manage those Pods directly for normal lifecycle tasks; you change the Deployment and let Kubernetes do the rest.
Services: stable networking for changing Pods
Pods come and go, and each Pod gets its own IP. A Service provides a stable network endpoint (IP and DNS name) and a simple way to load-balance across the current set of Pods that match a label selector. Services decouple clients from Pod lifecycle changes: you can scale or replace Pods without changing how clients address your application. (kubernetes.io)
Service types you’ll see early on:
- ClusterIP (default): reachable only inside the cluster by a stable cluster IP and DNS name.
- NodePort: opens a high-numbered port on every node so external clients can reach the Service (useful for simple access on bare-metal).
- LoadBalancer: requests a cloud load balancer from the cloud provider and maps it to the Service (typical on managed clusters).
A simple Service that points at the Deployment’s Pods:
apiVersion: v1
kind: Service
metadata:
name: hello-svc
spec:
selector:
app: hello
ports:
- protocol: TCP
port: 80 # service port
targetPort: 8080 # containerPort on the Pod
type: ClusterIP
Note the important port distinction:
- port is the port clients use when talking to the Service.
- targetPort is the port on the container (inside the Pod).
You can also create Services from existing Deployments with kubectl expose; tutorials and the official getting-started guides show examples of that workflow. (kubernetes.io)
How the three work together (typical flow)
- Define a Deployment that describes your app container and the desired replica count.
- Apply that Deployment; Kubernetes creates a ReplicaSet and the requested Pods.
- Create a Service that selects the Pods by label (the same label used in the Deployment template).
- Clients inside the cluster use the Service’s DNS name or ClusterIP; external clients use NodePort/LoadBalancer or Ingress in front of Services.
Why this layering is useful:
- Pods are ephemeral and can be recreated; Services provide a stable address.
- Deployments give you declarative scaling and safe rollouts; you never manually create or delete Pods to scale in normal operations.
- Services let multiple replicas be load-balanced and discovered without complex configuration.
Practical pitfalls beginners run into
- Label mismatch: If the Service selector labels don’t match the Pods, the Service will have no endpoints (no Pods to route to). Always check labels on Pods and the Service selector with kubectl get pods –show-labels and kubectl get svc -o yaml.
- Port mismatch: If targetPort doesn’t match the containerPort, traffic won’t reach the container. When in doubt, set targetPort to the same value as the container’s port.
- Ephemeral local state: Don’t store important data inside containers. If your app needs persistent storage, declare a PersistentVolumeClaim so data survives Pod replacement.
- Thinking Pod == Deployment: A single Pod created manually will not be recreated if it’s deleted — a Deployment is the object that restores desired state.
Useful troubleshooting commands:
- kubectl get pods, kubectl describe pod
- kubectl logs
[-c container-name] - kubectl get svc, kubectl describe svc
- kubectl rollout status deployment/
Minimal example workflow (commands only)
Below is a minimal command sequence that creates a Deployment and exposes it with a Service of type NodePort (shows how the pieces fit together):
kubectl apply -f hello-deployment.yaml
kubectl apply -f hello-service.yaml # service that selects app: hello
kubectl get pods -l app=hello
kubectl get svc hello-svc
kubectl rollout status deployment/hello-deployment
The official Kubernetes tutorials provide small, interactive examples you can follow to see this in action. (kubernetes.io)
Closing summary
- Think of a Pod as “where containers run,” a Deployment as “who keeps the desired number of Pods running and handles updates,” and a Service as “the stable address clients use to reach those Pods.”
- Use labels consistently so Services can find the Pods created by your Deployments.
- Let Deployments manage Pods instead of hand-editing Pod objects; use Services to hide Pod churn from clients.
These three objects together form the fundamental pattern for running and exposing applications in Kubernetes. The official Kubernetes documentation and tutorials are excellent next references if you want to inspect the exact fields and behaviors used in manifests and rollout strategies. (kubernetes.io)