What Is Kubernetes
Kubernetes is a container orchestration platform. Google built the first version and later donated the project to the Cloud Native Computing Foundation. Teams use Kubernetes to run, scale, and manage containerized applications across many machines.
A container packages your app with everything it needs to run: code, runtime, libraries, and settings. Docker made containers easy to build and run on a single machine. But production systems rarely run on one machine. You need many containers, spread across many servers, staying healthy around the clock. That is the job Kubernetes does.
Kubernetes takes a cluster of machines and turns them into one large, programmable system. You describe the state you want, for example "run five copies of this app," and Kubernetes keeps that state running. If a container crashes, Kubernetes restarts it. If a server dies, Kubernetes moves the workload to a healthy server. You stop managing individual containers by hand and start managing desired outcomes.
Docker vs Kubernetes: Different Jobs
Comparing Docker and Kubernetes as if they compete misses the point. They solve different problems.
Docker builds container images and runs them on a single host. Docker Engine handles image creation, container startup, networking on one machine, and local storage.
Kubernetes manages containers across a fleet of machines. Kubernetes does not build images. Kubernetes schedules containers onto servers, restarts failed containers, scales apps up and down, routes network traffic, and rolls out updates without downtime.
Here is a direct comparison:
| Task | Docker | Kubernetes |
|---|---|---|
| Build a container image | Yes | No |
| Run a container on one machine | Yes | Yes, across many machines |
| Restart a crashed container automatically | Limited | Built in |
| Scale to 50 replicas on demand | Manual scripting | One command |
| Load balance traffic across replicas | Manual setup | Built in (Services) |
| Roll out a new version with zero downtime | Manual scripting | Built in (rolling updates) |
| Self-heal after a server failure | No | Yes |
| Manage secrets and configuration at scale | Basic | Built in (ConfigMaps, Secrets) |
Most production setups use both. Docker builds the image. Kubernetes runs that image reliably at scale. A pro DevOps engineer does not choose one over the other. The engineer picks Kubernetes when the app needs to survive failures, scale automatically, and deploy safely across a cluster, not just a laptop or single server.
Why Kubernetes Wins for Production Workloads
Plain Docker on a single host has real limits once traffic grows or uptime matters.
- No automatic failover. If the host machine goes down, every container on it goes down with no automatic recovery.
- No built-in scaling logic. You write your own scripts to add or remove containers based on load.
- No native load balancing across multiple hosts.
- No rolling updates. A bad deployment can take your app offline until you fix and redeploy manually.
- No declarative state management. You track what should be running yourself, often through custom scripts.
Kubernetes solves each of these directly:
- Self-healing: Kubernetes detects failed containers and replaces them automatically.
- Horizontal scaling: you set a target replica count, or let the Horizontal Pod Autoscaler adjust replicas based on CPU or custom metrics.
- Built-in load balancing: Services distribute traffic across every healthy replica.
- Rolling updates and rollbacks: Kubernetes updates your app one replica at a time and rolls back automatically if health checks fail.
- Declarative configuration: you write YAML files describing the desired state, and Kubernetes keeps the cluster matching that state.
This is the real reason engineering teams adopt Kubernetes. Uptime, recovery speed, and scaling all become automated instead of manual.
Kubernetes Architecture at a Glance
A Kubernetes cluster has two main parts.
Control plane
The control plane makes decisions for the cluster. Key components:
- API server: the front door for every command you send to the cluster.
- etcd: the database that stores the cluster's current state.
- Scheduler: decides which node runs each new Pod.
- Controller manager: watches the cluster and works to match the actual state to the desired state.
Worker nodes
Worker nodes run your actual application containers. Key components:
- Kubelet: the agent that talks to the control plane and manages containers on that node.
- Container runtime: the software that runs containers, such as containerd.
- Kube-proxy: handles network rules so traffic reaches the right Pod.
You send commands to the API server, usually through the kubectl command line tool. The control plane then works with the worker nodes to make your request real.
Types of clusters you will run into
- Managed cloud clusters: Google GKE, Amazon EKS, and Azure AKS run the control plane for you. You manage worker nodes and workloads only.
- Self-managed clusters: tools like kubeadm let you run every control plane component yourself, on your own servers.
- Local development clusters: Minikube, kind, and k3d spin up a small cluster on your laptop for testing before you push to a real environment.
A cluster can run on three nodes or three thousand nodes. The same YAML files, the same kubectl commands, and the same Deployment and Service objects work at both sizes. That consistency is a core reason teams standardize on Kubernetes across every environment.
Pods Explained
A Pod is the smallest deployable unit in Kubernetes. A Pod wraps one or more containers that share the same network address and storage.
Most Pods run a single container. Kubernetes still uses the Pod as the base unit because some apps need a main container plus a helper container, called a sidecar, sharing the same network namespace. Examples include a logging agent or a proxy running next to your app container.
Pods are temporary. Kubernetes creates and destroys Pods constantly during scaling, updates, and failure recovery. You rarely create standalone Pods in production. Instead, you manage Pods through higher-level objects like Deployments.
A minimal Pod definition looks like this:
apiVersion: v1
kind: Pod
metadata:
name: my-app-pod
spec:
containers:
- name: my-app
image: my-app:1.0
ports:
- containerPort: 3000
ReplicaSets and Deployments
A ReplicaSet keeps a specified number of identical Pods running at all times. If a Pod dies, the ReplicaSet creates a replacement.
You rarely write a ReplicaSet directly. You use a Deployment instead. A Deployment manages ReplicaSets for you and adds rolling updates, rollbacks, and version history on top.
Here is a Deployment for a Node.js app running three replicas:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app-deployment
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: my-app
image: my-app:1.0
ports:
- containerPort: 3000
Apply this file with:
kubectl apply -f deployment.yaml
Kubernetes creates three Pods, spreads them across available nodes, and keeps three running at all times. Update the image tag and reapply the file, and Kubernetes rolls out the new version one Pod at a time, checking health along the way.
Services Explained
Pods get a new internal IP address every time Kubernetes recreates them. Your frontend cannot reliably connect to a backend if the backend's address keeps changing. A Service solves this.
A Service gives a stable network address and DNS name to a group of Pods, selected by label. Traffic sent to the Service gets distributed across every matching, healthy Pod.
apiVersion: v1
kind: Service
metadata:
name: my-app-service
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
type: ClusterIP
Common Service types:
- ClusterIP: exposes the Service inside the cluster only. Default type.
- NodePort: exposes the Service on a static port on every node, reachable from outside the cluster.
- LoadBalancer: provisions an external load balancer through your cloud provider.
- ExternalName: maps the Service to an external DNS name.
Deployments keep your Pods running. Services keep traffic flowing to them, no matter which Pods come and go.
Namespaces, ConfigMaps, and Secrets
Three more building blocks come up in daily Kubernetes work.
- Namespaces split one cluster into isolated sections. Teams commonly run separate namespaces for development, staging, and production.
- ConfigMaps store non-sensitive configuration data, like environment variables or app settings, separately from your container image.
- Secrets store sensitive data, like API keys and database passwords, in a format Kubernetes handles with tighter access controls than a ConfigMap.
Storing configuration outside your image means you rebuild the image less often and swap settings per environment without touching code.
Volumes: Persistent Storage for Pods
A container's filesystem disappears the moment that container stops. Fine for a stateless web server. A problem for a database or any app that writes files you need to keep.
Kubernetes solves this with Volumes.
Volume
A Volume attaches storage to a Pod. The storage lives as long as the Pod does, and every container in that Pod can read and write to it. A basic emptyDir Volume gives containers in the same Pod a shared scratch space that survives container restarts but not Pod deletion.
PersistentVolume (PV)
A PersistentVolume is a piece of storage provisioned at the cluster level, separate from any single Pod. Your platform team, or your cloud provider through dynamic provisioning, sets up PVs backed by real disks, network storage, or cloud storage services.
PersistentVolumeClaim (PVC)
A PersistentVolumeClaim is a request for storage from a Pod. You define how much space you need and what access mode you need, and Kubernetes binds your claim to a matching PersistentVolume. The Pod mounts the PVC, not the raw storage directly.
StorageClass
A StorageClass defines a category of storage, for example fast SSD storage versus standard disk storage. Set a StorageClass on your PVC, and Kubernetes provisions the right kind of disk automatically instead of waiting for a cluster admin to create one by hand.
Here is a PVC requesting 5Gi of storage, followed by a Pod that mounts it:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: app-data-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
storageClassName: standard
apiVersion: v1
kind: Pod
metadata:
name: app-with-storage
spec:
containers:
- name: my-app
image: my-app:1.0
volumeMounts:
- mountPath: /data
name: app-storage
volumes:
- name: app-storage
persistentVolumeClaim:
claimName: app-data-claim
The Pod above writes to /data, and that data stays intact across Pod restarts and rescheduling. Stateless apps like a Next.js frontend rarely need this. Databases, file uploads, and caches usually do.
A Complete Example, Start to Finish
Here is the full picture for deploying a simple app:
- Build your Docker image and push it to a registry.
- Write a Deployment YAML file setting your image, replica count, and container port.
- Write a Service YAML file to expose that Deployment.
- Apply both files with
kubectl apply -f. - Check status with
kubectl get podsandkubectl get services. - Update the image tag in the Deployment file and reapply it to ship a new version with zero downtime.
That workflow replaces manual server setup, manual restarts, and manual traffic routing with a few YAML files and one command line tool.
Deploying a Next.js App to Kubernetes, Step by Step
Here is the full path from a working Next.js app to a live app running on Kubernetes.
Step 1: Set your Next.js output mode
Open next.config.js and set standalone output. This trims your production build down to only the files your app needs at runtime.
// next.config.js
module.exports = {
output: "standalone",
};
Step 2: Write a Dockerfile
# Stage 1: install dependencies and build
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: run the production build
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
CMD ["node", "server.js"]
This two-stage build keeps your final image small. The builder stage installs dependencies and compiles your app. The runner stage copies over only the standalone output.
Step 3: Build and push your image
docker build -t myregistry/nextjs-app:1.0 .
docker push myregistry/nextjs-app:1.0
Replace myregistry with your actual registry, for example Docker Hub, GitHub Container Registry, or your cloud provider's registry.
Step 4: Write your Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: nextjs-app
spec:
replicas: 3
selector:
matchLabels:
app: nextjs-app
template:
metadata:
labels:
app: nextjs-app
spec:
containers:
- name: nextjs-app
image: myregistry/nextjs-app:1.0
ports:
- containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
Step 5: Write your Service
apiVersion: v1
kind: Service
metadata:
name: nextjs-app-service
spec:
type: LoadBalancer
selector:
app: nextjs-app
ports:
- port: 80
targetPort: 3000
Step 6: Apply both files
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml
Step 7: Check that your app is running
kubectl get pods
kubectl get service nextjs-app-service
The second command returns an external IP address once your cloud provider finishes provisioning the load balancer. Open that address in a browser, and your Next.js app loads.
Step 8: Ship a new version
Build a new image with a new tag, push it, then update the Deployment:
docker build -t myregistry/nextjs-app:1.1 .
docker push myregistry/nextjs-app:1.1
kubectl set image deployment/nextjs-app nextjs-app=myregistry/nextjs-app:1.1
Kubernetes rolls out the new image one Pod at a time. Your users stay connected the whole time, since the Service only routes traffic to Pods that pass their health check.
When You Should Use Kubernetes
Kubernetes adds real value once you need more than one server, automatic recovery from failure, and safe rolling deployments. A single small app on a single server may not need this complexity yet.
Reach for Kubernetes when your app needs to:
- Scale beyond what one machine handles.
- Stay online through hardware failures.
- Deploy updates without downtime.
- Run consistently across development, staging, and production.
- Manage many microservices instead of one monolith.
If your project is a side project on one small server, plain Docker or Docker Compose may serve you better for now. Kubernetes pays off at production scale.
Top comments (0)