What Are Docker and Kubernetes? The End of "It Worked on My Machine"
Docker puts your app in a box that runs the same everywhere; Kubernetes manages hundreds of those boxes. The difference, how they work together and when you actually need Kubernetes, with examples.

Contents 10
One of the oldest jokes in software goes: "But it worked on my machine!" The app runs perfectly on the developer's laptop, then crashes on the server because of a different Java version, a missing library or some other setting. Docker was born to fix that. Kubernetes was born to fix the problem Docker created: who manages hundreds of containers, and how?
In this post we explain both from scratch with real examples. At the end, we give an honest answer to the most important question: do you actually need Kubernetes?
In short:
- Docker packages your app with all its dependencies into a "container" that runs the same everywhere.
- Kubernetes runs many containers across many servers; it restarts what crashes, scales out under load and ships updates without downtime.
- They aren't rivals: Docker (or a compatible tool) builds the image, Kubernetes runs and manages it.
- For most small projects running a few services on one server, Docker Compose is enough; Kubernetes' complexity only pays off at scale.
What is a container, and how is it different from a VM?
The classic way to run an app elsewhere was a virtual machine (VM). A VM emulates a whole computer inside a physical one: its own operating system, its own kernel, gigabytes of disk. It can take minutes to boot.
A container is a much lighter idea. All containers share the same operating system kernel; each one carries only the files, libraries and settings the app needs. Thanks to Linux features that isolate processes (namespaces) and limit resources (cgroups), each container behaves as if it had its own machine.
| Virtual machine | Container | |
|---|---|---|
| Contains | Full OS + app | Just the app and its dependencies |
| Size | Usually gigabytes | Usually megabytes |
| Startup | Minutes | Seconds |
| Isolation | Very strong (separate kernel) | Strong, but shared kernel |
An analogy: a VM is like building a separate house for every tenant. A container is like building separate apartments in one building that share the infrastructure (water, power, foundations).
What does Docker do?
Docker is the tool that made building and running containers easy for everyone. It has three core concepts:
- Dockerfile: the recipe for how to prepare your app's box.
- Image: the immutable package built from the recipe. Build it once, and it runs the same everywhere.
- Container: a running instance of an image. You can start as many containers from one image as you like.
A Dockerfile for a simple Node.js app might look like this:
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Each line creates a layer: start from a lightweight Node.js base, install dependencies, copy the code and define the start command. Copying package.json before the rest of the code is deliberate: if your code changes but your dependencies don't, Docker reuses that layer from cache and the build takes seconds.
Building and running the image takes two commands:
docker build -t my-app:1.0 .
docker run -d -p 3000:3000 my-app:1.0
That image now runs identically on your laptop, the test server and production. That's the end of "it worked on my machine".
Docker Compose: running several services together
Real apps are rarely one piece. A website usually has a frontend, an API and a database. Docker Compose lets you define them in one file:
services:
api:
image: my-app:1.0
ports:
- "3000:3000"
environment:
DATABASE_URL: postgres://app:secret@db:5432/app
depends_on:
- db
db:
image: postgres:18
environment:
POSTGRES_USER: app
POSTGRES_PASSWORD: secret
volumes:
- pgdata:/var/lib/postgresql
volumes:
pgdata:
docker compose up -d brings both services up together. The API reaches the database by the name db; Compose sets up a network between them. Many companies run production on a single server this way, and it's a perfectly healthy approach. Many of our own projects, including our website, run like this.
Why would you need Kubernetes?
Docker is great on one machine. Things change when these questions show up:
- What happens to the containers if the server dies?
- When traffic grows tenfold, how do you run ten copies of the API, and who puts a load balancer in front?
- How do you ship a new version without users noticing any downtime?
- If the new version is broken, how do you roll back fast?
- Who places fifty services on twenty servers, and by what logic?
Kubernetes (K8s for short) is the answer. Built on lessons from Google's internal Borg system and open-sourced in 2014, it now lives under the Cloud Native Computing Foundation (CNCF). You tell Kubernetes what you want, and it keeps working to make it so. This is called declarative management.
Core Kubernetes concepts
- Cluster: all the machines Kubernetes manages.
- Node: each server in the cluster. Containers run on nodes.
- Control plane: the brain of the cluster. It decides what runs where and constantly watches the state.
- Pod: the smallest unit in Kubernetes. It usually holds one container; a few containers that must work together can share a pod.
- Deployment: a target like "always run 3 copies of this image". It replaces crashed pods and rolls out updates gradually.
- Service: puts a stable network address in front of changing pods and spreads traffic across them.
- Ingress: routes outside HTTP traffic to the right service by hostname and path.
- ConfigMap and Secret: keep settings and secrets (passwords, API keys) outside the image.
Your first Deployment
To run the Docker image above as three copies on Kubernetes, this YAML is enough:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 3
selector:
matchLabels:
app: my-app
template:
metadata:
labels:
app: my-app
spec:
containers:
- name: api
image: ghcr.io/mycompany/my-app:1.0
ports:
- containerPort: 3000
readinessProbe:
httpGet:
path: /health
port: 3000
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
memory: 256Mi
---
apiVersion: v1
kind: Service
metadata:
name: my-app
spec:
selector:
app: my-app
ports:
- port: 80
targetPort: 3000
Run kubectl apply -f app.yaml and Kubernetes gets to work. A few details matter here:
- replicas: 3 — Kubernetes always tries to run three pods. Delete one by hand and a new one appears within seconds.
- readinessProbe — a pod gets no traffic until
/healthanswers successfully, so a new version isn't shown to users before it's ready. - resources — how much CPU and memory each pod asks for. Kubernetes uses this to place pods on nodes; a container that exceeds its memory limit is restarted.
To ship a new version, change the image tag and apply the file again. Kubernetes replaces pods one by one (a rolling update). If something breaks, kubectl rollout undo deployment/my-app takes you back.
How do Docker and Kubernetes work together?
A common mistake is to see them as rivals. They're really two ends of one production line:
- A developer writes code, and an image is built from the Dockerfile.
- The image is pushed to a registry (Docker Hub, GitHub Container Registry, etc.).
- Kubernetes pulls the image from the registry and runs it on nodes in the cluster.
One detail: Kubernetes 1.24 removed dockershim, the layer needed to use Docker Engine directly as a runtime. Clusters today usually run containerd or CRI-O. That doesn't affect you: images built with Docker follow a common standard called OCI, so they run fine on these runtimes. Docker keeps its place on the developer's desk as the tool for building images.
Do you really need Kubernetes?
Let's be honest: Kubernetes is powerful but complex. Setting up a cluster, keeping it updated, monitoring and securing it takes real expertise. Many teams take on complexity they don't need too early and lose speed.
Docker Compose is probably enough if:
- Your app fits on one server,
- A few minutes of planned maintenance downtime is acceptable,
- Nobody on your team can spend full time on infrastructure.
Start considering Kubernetes if:
- You need to spread across several servers,
- Zero-downtime updates and autoscaling are business requirements,
- Different teams run dozens of microservices,
- High availability (service survives a server failure) is a must.
There's a middle path too: managed Kubernetes services like AWS EKS, Google GKE or Azure AKS run the control plane for you. For a taste of Kubernetes on a single server, lightweight distributions like k3s are a good start.
Frequently asked questions
Can I learn Kubernetes without learning Docker?
Not recommended. Kubernetes assumes you understand containers. Learn to write a Dockerfile, build images and run a few services together with Compose first; only then will you really understand the problems Kubernetes solves.
Did Kubernetes replace Docker?
No. Kubernetes now uses containerd or CRI-O as its runtime instead of Docker Engine, but Docker is still the most common tool for building images and developing locally. Images built with Docker run fine on Kubernetes.
Is Kubernetes expensive?
The software itself is open source and free. The cost comes from servers, managed service fees and, above all, the people who run it. For a small project that cost often outweighs the benefit.
Should my database run on Kubernetes too?
It's possible but needs care. Databases hold state (data) and can't be casually deleted and recreated like other pods. Many teams leave the database to a managed service (e.g. Amazon RDS) and run only the apps on Kubernetes.
Containers permanently changed how software is packaged and shipped. Picking the right tool at the right scale is an engineering decision. If you want infrastructure built on Docker, Compose or Kubernetes for your project, reach us through our web application development page.


