Kubernetes Pods, Deployments & Services: Deploy and Expose Your App
Learn how Kubernetes Pods, Deployments, and Services work. Write YAML manifests, deploy a containerised application, expose it with a Service, and perform rolling updates.

Understanding Pods, Deployments, and Services is the practical core of Kubernetes. Pods run your containers. Deployments manage Pods at scale. Services expose Pods to network traffic — from other Pods inside the cluster or from the outside world.
By the end of this lesson you'll have a containerised application running in Kubernetes, exposed via a Service, and updated with zero downtime.
Pods in Practice
A Pod is the smallest deployable unit in Kubernetes. You rarely create Pods directly — Deployments manage them for you — but understanding Pod manifests is essential because Deployment templates embed them.
A Minimal Pod Manifest
apiVersion: v1
kind: Pod
metadata:
name: my-api-pod
labels:
app: my-api
spec:
containers:
- name: api
image: myregistry/myapp:1.0
ports:
- containerPort: 3000
resources:
requests:
memory: "64Mi"
cpu: "100m"
limits:
memory: "128Mi"
cpu: "500m"Apply it:
kubectl apply -f pod.yaml
kubectl get pods
kubectl describe pod my-api-podResource Requests and Limits
Always set resource requests and limits on production containers:
- Requests: The minimum resources the container needs. The scheduler uses this to decide which node can run the Pod.
- Limits: The maximum the container can use. Kubernetes kills containers that exceed their memory limit.
resources:
requests:
memory: "128Mi" # 128 mebibytes
cpu: "250m" # 250 millicores = 0.25 CPU cores
limits:
memory: "256Mi"
cpu: "1000m" # 1 full CPU coreLiveness and Readiness Probes
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
failureThreshold: 3
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10- Liveness probe: If this fails repeatedly, Kubernetes restarts the container. Use for detecting deadlocks or crashed processes.
- Readiness probe: If this fails, Kubernetes removes the Pod from the Service's endpoint list — no traffic is sent to it. Use for detecting when the app is still initialising.
Deployments
A Deployment manages a set of identical Pod replicas and handles rolling updates. It's what you'll use for stateless application workloads.
A Production-Ready Deployment Manifest
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-api
namespace: default
labels:
app: my-api
spec:
replicas: 3
selector:
matchLabels:
app: my-api
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1
template:
metadata:
labels:
app: my-api
spec:
containers:
- name: api
image: myregistry/myapp:1.0
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: PORT
value: "3000"
resources:
requests:
memory: "128Mi"
cpu: "250m"
limits:
memory: "256Mi"
cpu: "1000m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 30
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 10Apply and verify:
kubectl apply -f deployment.yaml
kubectl get deployments
kubectl get pods
kubectl rollout status deployment/my-apiThe selector and template.labels Must Match
The selector.matchLabels tells the Deployment which Pods it owns. The template.metadata.labels are applied to the Pods it creates. They must match — otherwise the Deployment can't track its Pods.
Rolling Updates
With strategy: RollingUpdate, maxUnavailable: 1 means at most 1 Pod is unavailable during the update, and maxSurge: 1 means at most 1 extra Pod is created above the desired count. Kubernetes terminates old Pods and creates new ones gradually, ensuring the application stays available throughout.
To update to a new image version:
kubectl set image deployment/my-api api=myregistry/myapp:2.0
kubectl rollout status deployment/my-apiOr update the YAML and kubectl apply -f deployment.yaml — the preferred GitOps approach.
Rollback
kubectl rollout undo deployment/my-api
# or rollback to a specific revision:
kubectl rollout undo deployment/my-api --to-revision=2
kubectl rollout history deployment/my-apiServices
Pods have IP addresses, but those IPs change every time a Pod is recreated. A Service provides a stable DNS name and IP that always routes to healthy Pods.
Service Types
| Type | Description |
|---|---|
ClusterIP | Default. Internal IP only — accessible within the cluster |
NodePort | Exposes the service on each node's IP at a static port (30000–32767) |
LoadBalancer | Provisions a cloud load balancer (AWS ELB, GCP LB, etc.) |
ExternalName | Maps to an external DNS name (no proxying) |
ClusterIP Service (Internal)
apiVersion: v1
kind: Service
metadata:
name: my-api-service
spec:
selector:
app: my-api
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: ClusterIPOther Pods in the cluster can now reach your API at http://my-api-service:80.
NodePort Service (External Access Without Cloud LB)
apiVersion: v1
kind: Service
metadata:
name: my-api-nodeport
spec:
selector:
app: my-api
ports:
- protocol: TCP
port: 80
targetPort: 3000
nodePort: 30080
type: NodePortAccess the API at http://<NODE-IP>:30080. Useful for local development with minikube:
minikube service my-api-nodeport --urlLoadBalancer Service (Cloud)
apiVersion: v1
kind: Service
metadata:
name: my-api-lb
spec:
selector:
app: my-api
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancerOn AWS/GCP/Azure, this automatically provisions a cloud load balancer and assigns an external IP:
kubectl get service my-api-lb
# Watch EXTERNAL-IP column until it's assignedHow Services Find Pods: Labels and Selectors
The magic that connects Services to Pods is labels and selectors. Pods have labels (key-value metadata) and Services have selector (which Pods to route to). When the Service receives traffic, it routes to any Pod matching all selector labels that is Ready (passing its readiness probe).
# Pod labels
metadata:
labels:
app: my-api
# Service selector — must match
spec:
selector:
app: my-apiFull Deploy + Expose Workflow
# 1. Apply Deployment
kubectl apply -f deployment.yaml
kubectl rollout status deployment/my-api
# 2. Apply Service
kubectl apply -f service.yaml
kubectl get services
# 3. Test locally with port-forward
kubectl port-forward service/my-api-service 3000:80
curl http://localhost:3000/health
# 4. Scale up
kubectl scale deployment/my-api --replicas=5
kubectl get pods
# 5. Update image
kubectl set image deployment/my-api api=myregistry/myapp:2.0
kubectl rollout status deployment/my-api
# 6. Roll back if needed
kubectl rollout undo deployment/my-apiPrevious: Lesson 7 — Kubernetes Architecture | Next: Lesson 9 — ConfigMaps & Secrets
Part of the Docker & Kubernetes Mastery course.
External references:
