The short answer
Kubernetes is used to deploy, run, scale and heal containerised applications across a group of machines (a cluster). Instead of you logging into a server and starting containers by hand, you describe the state you want in a few YAML files, and Kubernetes keeps reality matching that description: the right number of copies running, traffic routed to healthy ones, crashed containers restarted, and new versions rolled out without downtime.
That is the textbook definition, and every vendor page says roughly the same thing. The more useful question for a web team is: which of my actual problems does this solve, and is it worth the complexity? This guide walks through the four objects you genuinely need to understand (pods, deployments, services and ingress) using one small Node.js app as the running example, and then gives you an honest answer on when Docker Compose or a managed platform is the better call.
What problems does Kubernetes actually solve for web apps?
Before the jargon, here is the list of real-world headaches that push teams toward orchestration:
- “The app died at 3am and nobody noticed.” Kubernetes restarts failed containers automatically and stops sending traffic to unhealthy ones.
- “Black Friday traffic melted our single server.” You can scale from 2 copies to 20 with one command, or automatically based on CPU or custom metrics.
- “Deployments mean five minutes of downtime.” Rolling updates replace old containers with new ones gradually, and roll back if the new version fails its health check.
- “We have 14 microservices and no idea which port is which.” Services give every workload a stable internal DNS name, so your API calls
http://ordersinstead of a hardcoded IP. - “Our servers are 8% utilised but we keep buying more.” The scheduler packs workloads onto nodes based on the CPU and memory they request.
- “It works on my machine / in staging / on that one server.” The same manifests deploy to a laptop cluster, a cloud cluster or on-prem hardware.
- “Only Dave knows how production is configured.” The whole environment lives in Git as declarative YAML, reviewable and revertible.
If you read that list and thought “none of those are my problem right now”, skip to the honest scoping section. You may not need Kubernetes at all, and that is a perfectly respectable engineering conclusion.

Our running example: a boring Node.js app
Imagine a small Express API that returns product data. It listens on port 3000, reads a database URL from an environment variable, and exposes /healthz for health checks.
// server.js
const express = require('express');
const app = express();
app.get('/healthz', (req, res) => res.status(200).send('ok'));
app.get('/api/products', (req, res) => res.json([{ id: 1, name: 'Tote bag' }]));
app.listen(3000, () => console.log('listening on 3000'));
You containerise it with a Dockerfile, push the image to a registry as registry.example.com/products-api:1.4.0, and now you have one unit of deployable software. Everything below is about what Kubernetes wraps around that image. It is argued more carefully on redhat.com.
Pods: the smallest thing Kubernetes runs
A pod is one or more containers that share a network namespace and storage volumes, scheduled together on the same node. In practice, 90% of web pods contain exactly one container: your app.
Key things to internalise:
- Containers in the same pod reach each other over
localhost. That is why sidecars (log shippers, proxies, auth agents) live in the pod with the app. - Each pod gets its own cluster-internal IP address. That IP changes every time the pod is recreated, which is exactly why services exist.
- Pods are disposable. Do not treat them as servers. They get evicted, rescheduled and replaced constantly, and that is by design.
- You almost never create pods directly. You create a deployment, which creates pods for you.
Two settings inside the pod spec do most of the work for web apps:
- Probes:
livenessProbe(restart me if I am wedged) andreadinessProbe(do not send traffic until I am warm). Our/healthzendpoint feeds both. - Resource requests and limits: how much CPU and memory the scheduler should reserve, and the ceiling before the container gets throttled or killed. Getting these wrong is the single most common cause of mysterious restarts.

Deployments: how you actually ship the app
A deployment is the object you write. It says “I want N identical pods running this image, and when I change the image, replace them gradually.” Behind the scenes it manages a ReplicaSet, which manages the pods. Much the same conclusion turns up on coursera.org.
apiVersion: apps/v1
kind: Deployment
metadata:
name: products-api
spec:
replicas: 3
selector:
matchLabels:
app: products-api
template:
metadata:
labels:
app: products-api
spec:
containers:
- name: api
image: registry.example.com/products-api:1.4.0
ports:
- containerPort: 3000
env:
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: products-secrets
key: database-url
readinessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 3
livenessProbe:
httpGet:
path: /healthz
port: 3000
initialDelaySeconds: 15
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256Mi
What you get from those 35 lines:
- Self-healing. Kill a pod, delete a node, crash the process: the controller notices the gap between desired (3) and actual, and creates a replacement.
- Rolling updates. Change the image tag to
1.5.0and apply. Kubernetes starts new pods, waits for them to pass the readiness probe, then terminates old ones. No downtime, no load balancer surgery. - Rollback.
kubectl rollout undo deployment/products-apiputs you back on the previous ReplicaSet in seconds. - Scaling.
kubectl scale deployment/products-api --replicas=10, or attach a HorizontalPodAutoscaler to do it on CPU or request-rate metrics.
Other workload types you will meet
| Object | Use it for |
|---|---|
| Deployment | Stateless web apps, APIs, workers. Your default. |
| StatefulSet | Databases and anything needing stable identity and persistent disks per replica. |
| DaemonSet | One pod per node: log collectors, monitoring agents. |
| Job / CronJob | One-off migrations, nightly exports, scheduled cleanups. |
Services: a stable address for moving targets
Pod IPs churn. A service gives a set of pods one permanent virtual IP and a DNS name, and load balances across whichever pods currently match its label selector.
apiVersion: v1
kind: Service
metadata:
name: products-api
spec:
selector:
app: products-api
ports:
- port: 80
targetPort: 3000
Now any other pod in the cluster can call http://products-api (or http://products-api.default.svc.cluster.local) and reach a healthy replica. Your frontend service never needs to know how many API pods exist or where they live.
The service types, decoded
| Type | What it does | When to use it |
|---|---|---|
| ClusterIP | Internal-only virtual IP (the default) | Service-to-service calls. Use this for almost everything. |
| NodePort | Opens a high port on every node | Local testing and odd edge cases. Rarely production. |
| LoadBalancer | Provisions a cloud load balancer with a public IP | Exposing one entry point. One per service gets expensive fast. |
| ExternalName | CNAME to an outside host | Pointing at a managed database or third-party API. |

Ingress: one front door, many apps
Giving every service its own cloud load balancer is wasteful. Ingress is an HTTP routing layer: one load balancer, backed by an ingress controller (nginx, Traefik, HAProxy, or your cloud’s native one), that routes by hostname and path to the right service, and terminates TLS.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: storefront
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts: ["shop.example.com"]
secretName: shop-tls
rules:
- host: shop.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: products-api
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: storefront-web
port:
number: 80
Pair it with cert-manager and your HTTPS certificates renew themselves. This is the piece that usually convinces frontend-heavy teams that the YAML tax is worth paying: one manifest replaces a pile of hand-edited nginx config and a calendar reminder to renew certificates.
Note: the newer Gateway API is the long-term successor to Ingress and is now the direction of travel for advanced routing (traffic splitting, header-based rules, multi-team ownership). Ingress still works fine and is not going anywhere soon, so start there and migrate when you need the extra power.
Config and secrets: the two objects nobody mentions but everyone needs
- ConfigMap: non-sensitive configuration (feature flags, API base URLs, log levels) injected as environment variables or mounted files.
- Secret: database passwords, API keys, TLS certificates. Base64-encoded by default, so enable encryption at rest and use an external secret manager for anything serious.
Because your image stays identical across environments and only the ConfigMap changes, you get genuine build-once-deploy-anywhere behaviour.
How the pieces fit together
For our Node.js app, the full request path looks like this:
- A browser resolves
shop.example.comto the cloud load balancer’s IP. - The load balancer forwards to the ingress controller pods.
- The ingress rule matches
/apiand forwards to the products-api service. - The service load balances across the ready pods created by the deployment.
- One pod’s Node.js container handles the request and replies.
And the control loop that makes it all self-managing: you declare desired state, controllers watch actual state, and they reconcile the difference forever. That single idea is the whole of Kubernetes. Everything else is an implementation detail.

The honest part: when you do NOT need Kubernetes
Most vendor articles stop before this section, because they sell clusters. Here is the trade-off nobody puts in the brochure: Kubernetes solves distributed-systems problems by introducing a distributed system. You inherit cluster upgrades, CNI plugins, RBAC, admission controllers, node autoscaling, certificate rotation and a monitoring stack. That is a real, ongoing job.
Docker Compose is enough when…
- You run on one server (or one server per environment) and can tolerate a short restart during deploys.
- You have fewer than roughly five services and no traffic spikes you cannot predict.
- Your team is one to five developers with no dedicated ops capacity.
- Your uptime target is “good”, not “four nines with an SLA”.
A managed platform (PaaS) is enough when…
- Your app is a standard web app plus a database plus a queue.
- You want git push to deploy, automatic TLS, and autoscaling without writing YAML.
- You would rather pay a premium per instance than pay a salary for cluster maintenance. Options include app platforms from the major clouds, container-as-a-service offerings like Cloud Run or ECS Fargate, and developer-focused PaaS providers.
Kubernetes earns its keep when…
- You run many services (say 8 or more) with different scaling and release cadences.
- You need multiple environments that must behave identically, including ephemeral preview environments per pull request.
- You have real availability requirements and need traffic to survive a node or zone failure.
- You are multi-cloud, hybrid or on-prem, or have data residency constraints that rule out a single PaaS.
- You need fine-grained control: GPU scheduling, custom operators, service mesh, strict network policies, compliance-driven isolation.
- You have at least one person who wants to own the platform, or budget for managed control planes plus support.
Decision table
| Your situation | Best fit | Why |
|---|---|---|
| Side project, marketing site, MVP | PaaS or a single VM with Compose | Complexity buys you nothing yet |
| Monolith plus database, steady traffic | PaaS or container service | Autoscaling without cluster ops |
| Spiky traffic, zero-downtime releases required | Managed Kubernetes | Rolling updates and HPA out of the box |
| 10+ microservices, several squads | Kubernetes | Namespaces, RBAC, shared platform primitives |
| Regulated, on-prem or hybrid | Kubernetes | Same API everywhere, strong isolation controls |
| Bursty batch jobs and ML workloads | Kubernetes | Jobs, node pools, GPU scheduling |
Rule of thumb: if you cannot name three concrete problems from the list at the top of this article that are hurting you today, choose the simpler option and revisit in six months. Migrating from Compose to Kubernetes later is far cheaper than debugging a cluster you did not need.
Kubernetes vs Docker: they are not competitors
This comes up constantly, so let us settle it:
- Docker builds images and runs containers on a single machine. It is the packaging and runtime layer.
- Docker Compose runs a handful of containers together on a single machine. Great for local development.
- Kubernetes runs containers across many machines, with scheduling, networking, scaling and self-healing. It is the orchestration layer.
You still build with Docker (or Buildpacks, or BuildKit) and then hand the resulting OCI image to Kubernetes. Kubernetes dropped the Docker runtime shim years ago and uses containerd or CRI-O under the hood, but images built with Docker work exactly the same.

Why some teams move away from Kubernetes
You will see this question in search suggestions, and it deserves a straight answer. Teams walk back from Kubernetes for four recurring reasons:
- They adopted it too early. Three services and two developers do not need a cluster. The operational overhead outweighed the benefit.
- Cost surprise. Idle node capacity, per-load-balancer charges, an observability stack and egress fees add up. Serverless containers can be cheaper for lumpy workloads.
- Cognitive load. Debugging requires understanding pods, DNS, CNI, service meshes and admission webhooks. That knowledge tends to concentrate in one or two people.
- Better-fitting alternatives appeared. Managed container runtimes now cover a lot of what people originally needed clusters for.
None of that means Kubernetes is bad. It means it is a platform-building tool, and platform building is only worth it at a certain scale. Plenty of large organisations would be worse off without it.
A realistic learning path for web developers
You do not need to learn all of Kubernetes. Learn the 20% that runs web apps:
- Containerise one app properly: small base image, non-root user, health endpoint, config via environment variables.
- Spin up a local cluster with kind, k3d or Docker Desktop. It costs nothing and deletes cleanly.
- Write a deployment and a service for your app. Break things on purpose: delete a pod, watch it come back.
- Add an ingress controller and route two apps through one hostname.
- Learn five commands well:
kubectl get,describe,logs,exec,apply. That is 90% of daily use. - Add ConfigMaps, Secrets, resource requests and an HPA.
- Templating with Helm or Kustomize, then GitOps with Argo CD or Flux once you have more than one environment.
A focused developer can be productive in a week or two. Becoming the person who operates a production cluster is a much longer road, which is the strongest argument for using a managed control plane from your cloud provider.
FAQ
What is Kubernetes used for, in simple words?
It is an automatic manager for containers. You tell it what should be running and how many copies; it finds machines to run them on, restarts anything that fails, sends traffic to healthy copies, and swaps in new versions without downtime. Much the same conclusion turns up on plural.sh.
What is Docker vs Kubernetes?
Docker packages and runs a container on one machine. Kubernetes coordinates many containers across many machines. They work together: Docker (or any OCI-compatible tool) builds the image, Kubernetes decides where and how it runs.
What is the purpose of using Kubernetes?
To make deployment, scaling and recovery declarative and automatic, so infrastructure behaviour lives in version-controlled files rather than in someone’s SSH history. Secondary benefits: portability across clouds, better hardware utilisation, and a common platform for multiple teams.
What is a container in Kubernetes?
The same container you know from Docker: an isolated process with its own filesystem and dependencies. In Kubernetes, containers always run inside a pod, and the pod is the unit that gets scheduled onto a node.
Can I learn Kubernetes in 2 days?
You can learn enough to deploy a Node.js app with a deployment, service and ingress in two focused days using a local cluster. Operating a production cluster safely (upgrades, networking, security, cost control) takes months of real exposure.
Is Kubernetes only for microservices?
No. A containerised monolith runs perfectly well on Kubernetes and still gets self-healing, rolling updates and autoscaling. Microservices simply make the benefits more obvious because there is more to coordinate.
Do I need Kubernetes for a single web app?
Usually not. One app on one server is a job for Docker Compose or a managed platform. Consider Kubernetes when you have multiple services, strict uptime targets, unpredictable traffic, or infrastructure constraints that a PaaS cannot meet.
Who uses Kubernetes?
Large tech companies, most cloud providers, banks, telcos, retailers and a long tail of mid-sized engineering teams. It has become the default substrate for internal developer platforms, which is why so many tools now assume it exists.
Key takeaways
- Pod = the running unit. Deployment = how many and which version. Service = stable internal address. Ingress = public HTTP front door with TLS.
- Kubernetes is used for deploying, scaling, healing and routing containerised workloads across a cluster, declaratively.
- It shines with many services, real uptime requirements, and multi-environment or hybrid setups.
- For a single app with predictable traffic, Docker Compose or a managed platform will ship faster and cost less.
- If you do adopt it, start with a managed control plane and learn the five kubectl commands before the fifty extra CRDs.

