Docker packages your app into an image; Kubernetes decides where, how many, and how to keep that image's containers healthy across many machines. They are not rivals — Docker is containerization, Kubernetes is orchestration. This guide compares head-to-head, shows the one artifact workflow (Docker builds, Kubernetes or Docker runs), and gives a decision tree for when you need one, both, or neither.
- Docker: container platform —
Dockerfile → docker build -t myapp:1.0 . → docker run -d -p 3000:3000 myapp:1.0on one host; Compose addsapp + db + cacheon that host (docker compose up -d). Simple, fast, low overhead. Family includes Docker Engine, BuildKit, and Docker tools for Dockerfiles and Compose. - Kubernetes (K8s): orchestrator — declare
replicas:3, image:myapp:1.0in a Deployment; controllers keep 3 healthy Pods across Nodes, self-heal, bin-pack, HPA60% → 3→10, RollingUpdate25%zero-downtime. Needs cluster (managed EKS/GKE/AKS). Tooling via Kubernetes tools for Deployments and checks. - Are they rivals? No — complementary. Kubernetes runs containers; it previously used Docker runtime (containerd now) but image spec is OCI — you build with Docker/podman/buildah and run the same image on Docker host or K8s.
- Workflow you actually use: Build once with Docker family → push to Registry (Hub/GHCR) → Run:
docker run(1 VM) orkubectl apply -f deployment.yaml(N VMs). You always need Docker to build; you add K8s when you need orchestration. - Decision: 1 service on 1 VM → Docker/Compose alone. 3+ services needing HA/scale/rollout/GitOps → Docker (build) + Kubernetes (run). Want containers without K8s ops → Cloud Run/ECS/Nomad.
What Is Docker — Build, Ship, and Run One Container (or Many on One Host)
Docker packages app + libs into an image — a read-only layered filesystem built from a Dockerfile. That image runs as a container — an isolated process with its own FS, network namespace, and cgroups limits, sharing the host kernel.
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./ && npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm","start"]
# Build & run:
docker build -t myapp:1.0 .
docker run -d -p 3000:3000 --name myapp myapp:1.0
docker logs myapp -f # inspect
# Share:
docker tag myapp:1.0 ghcr.io/org/myapp:1.0 && docker push ghcr.io/org/myapp:1.0
For many containers on the same host, Compose is Docker's multi-service YAML (compose.yml): services: web: build: . ports: ["3000:3000"] depends_on: [db], db: image: postgres:16 volumes: [pgdata:/...] → docker compose up -d creates network, volumes, and containers on that host. Compose is still single-host orchestration — great for dev and 1-VM prod. Docs: Docker Get Started, Compose, Dockerfile. Explore generation: we've curated Docker tools for Dockerfiles and Compose without YAML guesswork.
Docker Strengths and Limits
- ✓ Build once, run anywhere the same unstaged image — laptop, CI, prod.
- ✓ Isolation per container, MBs, ms start, 10k+ containers per host possible (with tuning).
- ✓ Simple: 8 commands cover 80% —
build/run/ps/logs/exec/stop/rm/compose up/down. - ✗ Single host: you pick
vm1vsvm2, ssh to restart crashed nodes, manually scaledocker compose --scale web=3only on that host with manual port mapping.
What Is Kubernetes — Orchestrate Many Containers Across Many Hosts
Kubernetes (K8s, from Borg) is the warehouse robot for containers. You declare desired state in YAML (replicas:3, image:myapp:1.0), controllers continuously reconcile observed (etcd) vs desired.
apiVersion: apps/v1
kind: Deployment
metadata: { name: myapp }
spec:
replicas: 3
selector: { matchLabels: { app: myapp } }
template:
metadata: { labels: { app: myapp } }
spec:
containers:
- name: app
image: ghcr.io/org/myapp:1.0
ports: [{ containerPort: 3000 }]
readinessProbe: { httpGet: { path: /ready, port: 3000 } }
resources: { requests: { cpu: 100m, memory: 128Mi } }
Apply: kubectl apply -f deployment.yaml → API validates → etcd → scheduler binds Pods to Nodes → kubelet runs containers → Service gives stable ClusterIP → Ingress routes api.example.com/api → Service. Controllers keep 3 healthy, reschedule on node death, HPA scales 3→10 at 60% CPU, RollingUpdate with maxUnavailable:25% zero-downtime. Deep: What is Kubernetes, Architecture, Deployments. Generate and validate via Kubernetes tools for deployments and best-practice checks.
Kubernetes Strengths and Limits
- ✓ Self-healing, bin-packing by
requests, declarative GitOps, Service discovery (CoreDNS), Ingress L7, autoscaling HPA+VPA+Cluster Autoscaler, canary via Argo Rollouts. - ✗ Complexity: control plane (etcd quorum 3), learning curve (Pod/Deployment/Service/Ingress/ConfigMap/Secret/Namespace), cost (managed control plane ~$70/mo + workers).
Architecture Compared — One Host vs Cluster, Same Runtime Underneath
Docker host: Host OS → Docker Engine (daemon, one machine) → containers web, db, cache on a bridge network, volumes local. You are the scheduler and the on-call.
K8s cluster: Control plane (API, etcd, Scheduler, Controller Manager) → many Workers (each: kubelet + kube-proxy IPVS + containerd + CNI/CSI) → Pods spread, Service VIP, Ingress. K8s is the scheduler/healer.
Runtime today: both use containerd (or CRI-O). Since K8s 1.24, dockershim is removed — K8s never needed Docker daemon, only the OCI image spec. So you can build with docker build, podman build, buildah, kaniko, or BuildKit — all produce images K8s runs via containerd identically. See Dockershim removal FAQ and Docker Build.
Head-to-Head — At-a-Glance Table
| Area | Docker (Compose) | Kubernetes |
|---|---|---|
| Scope | One host | Many hosts (cluster) with HA control plane |
| Unit | Container / Compose Service | Pod (1+ containers) → Deployment → Service |
| Scheduling | Manual / restart policy per host | Scheduler, bin-packing, affinity/anti-affinity, taints/tolerations, PDB |
| Healing | restart: unless-stopped on that host | Self-heal + reschedule on node NotReady, livenessProbe restarts |
| Scaling | Manual docker compose --scale one host, manual LB | HPA (60% CPU → 3→20), VPA, Cluster Autoscaler (pending Pods → nodes) |
| Updates | Recreate (downtime) or hand-rolled blue-green | RollingUpdate maxUnavailable:25% zero-downtime, canary via Rollouts/Flagger |
| Networking | Bridge, -p host:container | Service ClusterIP VIP, DNS myapp.svc, Ingress L7, NetworkPolicy |
| Storage | Volumes/bind mounts local | PVC/StorageClass, StatefulSet stable data-db-0 |
| Config | env_file, .env | ConfigMap/Secret (base64, encryption at rest), Helm/Kustomize |
| Ops | Simple, low overhead, ssh | Complex, GitOps (Argo CD/Flux), observability, cost |
You Need Both — The One-Artifact Workflow (Build with Docker, Run Anywhere)
The most common prod setup is not Docker vs K8s — it's Docker builds, K8s or Docker runs. The artifact is the OCI image — same bytes regardless of builder or runner.
# CI builds once (any builder: Docker, BuildKit, kaniko, podman)
docker build -t ghcr.io/org/myapp:1.2.3 .
docker push ghcr.io/org/myapp:1.2.3
# Run path A: single VM (Docker)
docker run -d -p 80:3000 ghcr.io/org/myapp:1.2.3
# Run path B: cluster (K8s)
kubectl set image deployment/myapp app=ghcr.io/org/myapp:1.2.3 --record
kubectl rollout status deployment/myapp
Docker alone flow: CI → docker build → push → ssh vm1 → docker pull && docker compose up -d — fine for one VM, internal tool, quick demo.
Docker + K8s flow: CI → docker build → push → kubectl set image → rollout (readiness-gated, 25% incremental) → Service stays available — same image, orchestrated.
Builder can vary: BuildKit, Podman, kaniko (build inside K8s without Docker daemon) all emit OCI. K8s document confirms via Images (kubectl) that any OCI image runs.
Composition vs Deployment — Concrete Compare
# compose.yml (one host)
services:
web:
build: .
ports: ["3000:3000"]
depends_on: [db]
db:
image: postgres:16
volumes: [pgdata:/var/lib/postgresql/data]
volumes: { pgdata: }
# K8s (many hosts) — same intent, distributed
# Deployment myapp replicas:3 → ReplicaSet → 3 Pods
# Service myapp ClusterIP 80 → targetPort http
# Ingress api.example.com /api → Service
# PVC gp3 10Gi StorageClass, ConfigMap LOG_LEVEL
Compose YAML becomes K8s manifests: build: . → Deployment image:, ports: → Service + Ingress, volumes: → PVC, env_file: → ConfigMap/Secret. Tools like Kompose auto-convert for starter manifests.
When to Use Which — Cost, Learning Curve, and Migration Path
Use Docker / Compose Alone When
- 1 service or 1–2 VMs, traffic <10k req/min, can tolerate brief restart windows.
- Internal tool, staging, demo, or early startup where team is 1–3 and ops time is scarce.
- Cost: single VM $5–20/mo vs managed K8s control plane ~$70/mo plus workers. Docker wins when K8s autoscale savings don't offset its overhead.
Use Docker (Build) + Kubernetes (Run) When
- 3+ services needing HA (node death shouldn't take you down), zero-downtime deploys, or canary.
- Autoscale by load (HPA 3→20) and bin-packing to keep workers full.
- GitOps audit: manifests in Git, Argo CD/Flux drift detection, rollback via
kubectl rollout undo.
Use Alternatives When You Want Containers Without K8s Ops
- AWS ECS/Fargate, Google Cloud Run, Azure Container Instances — run containers without managing K8s control plane; Fargate/Cloud Run scale to zero; less flexible than K8s (no DaemonSet/privileged, limited CNI).
- Nomad (HashiCorp) — simpler scheduler for mixed workloads (containers + VMs + binaries), one binary, less ecosystem than K8s.
Trade-off: these abstract orchestration — easier, but you lose K8s' ecosystem (Helm charts, operators, service meshes Istio/Linkerd).
Migration Path — No Rewrite, Just Add Manifests
- Docker alone → Docker + Compose: add
db/cachevia Composeservices, same Dockerfile + image. - Compose → Kubernetes: keep Dockerfile and image, add Kubernetes manifests: Deployment (replicas, probes, resources), Service (ClusterIP), Ingress (host/path), ConfigMap/Secret, PVC. Push same image tag,
kubectl apply -f k8s/. - Kubernetes → GitOps: add Argo CD Application syncing
k8s/Git repo → cluster.
Do explore manifests via our collections: Docker category for Dockerfile/Compose generators and Kubernetes category for Deployment generators and checkers — complementary tooling for the complementary platforms.
Common Misconceptions — Myth-Busting
| Myth | Reality |
|---|---|
| Kubernetes needs Docker | K8s needs containerd/CRI-O; any OCI builder (Docker, podman, kaniko) produces its images. Dockershim removed 1.24 per FAQ. |
| Kubernetes is always cheaper (bin-packing) | Bin-packing saves when workers are full (high utilization + HPA). With low utilization or many tiny services, control plane + under-filled nodes cost more than 1 VM. |
| Docker Swarm vs Kubernetes — pick one forever | Docker Swarm is simpler multi-host orchestration but tiny ecosystem vs K8s; most Swarm users migrate to K8s or to managed container services, not the reverse. |
| Compose vs Kubernetes — either/or | Use Compose for dev (fast compose up) and K8s for prod — kind (K8s in Docker) lets you keep one image for both. |
Deep Dive — Networking, Storage, and Secrets Compared
Three areas where the gap matters most in prod:
- Networking: Docker bridge (
docker0) +-p 3000:3000maps host→container on that host only. Compose creates a private bridge per stack (webfindsdbby name). Kubernetes CNI (Calico/Cilium) creates a flat Pod network (every Pod gets IP), ClusterIP VIP via kube-proxy IPVS survives Pod IP churn, and Ingress does L7 host/path routing with TLS termination. NetworkPolicy then locks it down — deny-by-default, allowfrontend→apionly — no Docker Compose equivalent. Docs: Docker networking, Kubernetes Service, NetworkPolicies. - Storage: Docker volumes are local to that host — move the container to another host and volume stays behind. Compose
pgdata:/var/lib/postgresql/datais host-local. Kubernetes PVC + StorageClass provisions a cloud volume (EBS/gpd, PD) that follows the Pod via scheduler, and StatefulSet'svolumeClaimTemplatesgives each replicadata-db-0, data-db-1stable across reschedules. Without this, 3 Redis replicas on Docker all share host path; on K8s StatefulSet they each keep independent data. - Secrets: Docker uses
.envor--env-file— plaintext on host, risk ofCOPY .envinto image. Kubernetes Secret is base64 in etcd (enable encryption at rest or external KMS/Vault), mounted as env or file, RBAC-controlled per Namespace, and not baked into image layers. For rotation, use external Secrets Operator rather than rebuilding images.
Scaling and Deploy Strategies — What You Actually Gain
Two scenarios show why teams move:
Scaling: On Docker, docker compose --scale web=3 creates 3 containers but you still publish ports manually (3000,3001,3002) and put a manual HAProxy in front. On Kubernetes, replicas:10 + HPA averageUtilization:60% spreads across nodes via scheduler bin-packing by requests, CA adds nodes when Pending, and Service load-balances via IPVS — one declaration, no manual port juggling.
Deploy: Docker pull && docker stop app && docker run new:tag is minutes of downtime per host, or a hand-rolled blue-green with two Compose files. Kubernetes RollingUpdate with readinessProbe does new ReplicaSet 1→ old 2→ new 2→ old 1→ new 3 with maxUnavailable:25% — Service removes Terminating Pods from endpoints before killing. Rollback is kubectl rollout undo scaling old ReplicaSet back — versioned. Add PodDisruptionBudget minAvailable:2 so drains never take you below 2. See Rolling Update.
Cost Reality — When Kubernetes Saves vs When It Burns
Control plane cost is fixed: managed EKS/GKE/AKS ~$70/mo plus 3× workers (e.g., 3× t3.medium ~$90/mo) = ~$160/mo before load. A Docker VM (2 vCPU, 4GB) is ~$20/mo. If your utilization is 80% with spiky load, K8s bin-packing + HPA scale 3→10 at peak then down to 3 saves vs over-provisioning a big VM. If utilization is 15% with 2 services idle, you're paying for an idle cluster — stay on Docker VM or Cloud Run/ECS. FinOps: observe kubectl top pod p50/p95 over a week before setting requests — too high packs sparse, too low causes Pending.
Learning curve matters too: Docker 80% in days; Kubernetes 80% in weeks (Pod/Deployment/Service/Ingress/ConfigMap/Namespace). For a solo dev shipping an MVP, that time is better spent on product. For a 3-engineer team needing audit, RBAC, and GitOps, the curve amortizes.
Dev vs Prod — Compose for Dev, Kubernetes for Prod Is Valid
Many teams keep both intentionally: compose up locally (fast, no cluster) and K8s in prod (HA, autoscale). Bridge via kind (Kubernetes IN Docker) to test manifests without cloud bill: kind create cluster && kubectl apply -f k8s/ && kubectl get pods. Or use Kompose to convert compose.yml → deployment.yaml as a starter then hand-tune probes/resources. This hybrid acknowledges they complement — not compete — and lets you use Docker tools locally and Kubernetes tools in CI.
# Start: Dockerfile already exists, image ghcr.io/org/app:1.0
# 1) Keep Dockerfile → build with Docker/BuildKit → push
# 2) Add k8s/deployment.yaml (replicas, probes, resources), service.yaml, ingress.yaml
# 3) kubectl apply -f k8s/ → 3 Pods across Nodes vs 1 container on 1 VM before
# 4) Add HPA + PDB + NetworkPolicy → production grade
No image rebuild — only orchestration layer changes. Your docker build artifact is reused.
Security and Learning Curve — Honest Trade-offs
Security: Docker default runs as root unless you add USER app in Dockerfile; secrets via --env-file can leak if you COPY .env into image. Kubernetes adds Pod Security Standards (restricted: runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation: false), RBAC per Namespace, and Secret encryption at rest — more to configure but finer grained. Neither is secure by default; both need explicit hardening.
Learning curve: Docker: understand Dockerfile, image, container, volume, network, registry — days. Kubernetes: add Pod, Deployment, ReplicaSet, Service, Ingress, ConfigMap, Secret, Namespace, RBAC, NetworkPolicy, PVC, HPA — weeks to productive, months to comfortable. Budget time: Docker solo MVP ships in a day; Kubernetes prod with probes, limits, PDB, and Ingress needs a week plus managed control plane. Use kind locally to practice without cloud bill.
Observability: Docker docker logs -f on one host is trivial but per-host. Kubernetes needs central logging (Loki/ELK) and metrics-server (kubectl top pod → until installed) plus events (kubectl get events show scheduler reasons). Without this, HPA and PDB are blind.
Real Stack Example — From 1 VM to Managed K8s
Start (Week 1, 1 developer): Dockerfile + docker run -d -p 80:3000 on a $10 VM. Deploys via scp. Works.
Grow (Month 3, 3 services): compose.yml with web, api, db on same VM — compose up -d one command, local volumes, .env file.
Scale (Month 9, need HA/scale): same images pushed to GHCR, add Deployment replicas:3 + Service + Ingress + HorizontalPodAutoscaler + PodDisruptionBudget; run on GKE Autopilot (control plane managed, pay per Pod). Rollouts become kubectl set image && rollout status, zero downtime, GitOps via Argo CD.
Key: image never changed format — only the runner. Teams that tried to rewrite Dockerfile for K8s wasted weeks; the Dockerfile from week 1 ran unchanged on K8s in month 9.
# Docker: does image run?
docker build -t myapp:test . && docker run --rm -p 3000:3000 myapp:test && curl -I http://localhost:3000/health
# K8s: does manifest apply dry-run?
kubectl apply --dry-run=client -f k8s/deployment.yaml && kubectl diff -f k8s/
Dry-run catches missing selector, imagePullSecrets, and readinessProbe before cluster rejects at apply.
Bottom line: you will always use Docker family to build images; add Kubernetes when you need a cluster brain to place, heal, and scale those images with declarative, auditable YAML — keep both skills, choose per workload size, not ideology.
Copy working server {} and Deployment blocks as templates — one correct block reused beats four hand-typed variants with different typos and missing probes.
Tip: keep Compose for dev even after adopting K8s prod — compose up stays the fastest inner loop, kind is for pre-prod manifest testing only.
Frequently Asked Questions
Is Kubernetes a replacement for Docker?
No. Docker builds and runs containers; Kubernetes orchestrates them. You still need to build images (with Docker/podman/kaniko) to run on K8s — K8s just decides placement, healing, scaling, and rolling updates across a cluster.
Can Kubernetes run without Docker?
Yes — it runs containers via containerd or CRI-O using the OCI spec. The builder can be Docker, but runtime since 1.24 is not Docker daemon. The dockershim FAQ details the switch.
Docker Compose vs Kubernetes — which for local dev?
Compose — one command compose up, no cluster, faster iteration. Use kind or k3d only when you need to test K8s manifests locally before prod.
Should I learn Docker first or Kubernetes first?
Docker first — understand Dockerfile → image → container → registry → volume/network in days, then K8s concepts (Pod/Deployment/Service/Ingress) map cleanly as "many containers, handled correctly."
Do I need both for a small startup?
Solo API on one VM: Docker/Compose alone is cheaper ($5–20 VM vs $70 K8s control plane) and simpler. Three services needing HA/zero-downtime/autoscale: Docker (build) + Managed K8s (run) saves more than it costs.
What about Docker Swarm?
Simpler multi-host Swarm mode exists in Docker, but Kubernetes won the ecosystem (Helm, operators, service mesh, HPA/CA). Swarm suits tiny Docker-native fleets; most serious multi-host needs pick K8s or Cloud Run/ECS.