Every developer hitting their first production deployment runs into this question: Do I need Docker, Kubernetes, or both?
It's the wrong framing. They don't compete. They operate at completely different layers of your infrastructure.
Let me be blunt upfront:
- Docker = packages and runs your app as a container (single host)
- Kubernetes = manages and scales those containers across many hosts
You don't choose between them. You choose when to graduate from one to the other.
What Docker Actually Does
Docker solves the "works on my machine" problem. It bundles your app code, runtime, libraries, and config into a container image — a portable artifact that runs identically everywhere Docker is installed.
Here's the simplest possible Dockerfile:
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["node", "server.js"]
Build it, push it, run it anywhere:
docker build -t my-api:1.0 .
docker push my-registry/my-api:1.0
docker run -p 3000:3000 my-registry/my-api:1.0
That image behaves the same on your MacBook, a CI runner, or a prod server. That's the whole value prop.
Docker Compose for Multi-Service Dev
For local development with multiple services, Docker Compose is your best friend:
# docker-compose.yml
services:
api:
build: ./api
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgres://user:pass@db:5432/myapp
depends_on:
- db
- redis
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: pass
redis:
image: redis:7-alpine
volumes:
pgdata:
docker compose up -d # spin everything up
docker compose logs -f # follow logs
docker compose down # tear it all down
One command spins up your entire local stack. This is where most teams should live until they genuinely need orchestration.
What Kubernetes Actually Does
Docker works great on one machine. The moment you need your app running across multiple machines — with auto-scaling, self-healing, load balancing, and zero-downtime deploys — Docker alone doesn't cut it.
Kubernetes (K8s) is the orchestration layer. It doesn't build containers. It runs and manages them across a cluster.
Here's what K8s handles automatically:
- Which node to schedule each container on
- Restarting crashed containers
- Scaling up/down based on CPU/memory load
- Distributing traffic across replicas
- Rolling out updates without downtime
- Managing secrets and config
- Network routing between services
The mental model: you declare your desired state, and K8s continuously reconciles reality toward it.
A Real Kubernetes Deployment
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-api
spec:
replicas: 3
selector:
matchLabels:
app: my-api
template:
metadata:
labels:
app: my-api
spec:
containers:
- name: api
image: my-registry/my-api:1.0
ports:
- containerPort: 3000
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "256Mi"
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 5
periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
name: my-api-svc
spec:
selector:
app: my-api
ports:
- port: 80
targetPort: 3000
type: ClusterIP
kubectl apply -f deployment.yaml
kubectl get pods
kubectl rollout status deployment/my-api
If one of those 3 pods crashes, K8s replaces it automatically. Scale on demand:
kubectl scale deployment my-api --replicas=10
Or set up auto-scaling based on CPU:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: my-api-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-api
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
Container vs Pod: The Confusion Cleared Up
This trips up everyone new to K8s.
Container = one packaged process. Docker's unit.
Pod = K8s's deployable unit. Wraps one or more containers that share:
- The same network namespace (they talk via
localhost) - The same storage volumes
- The same lifecycle
Most pods are single-container. But the sidecar pattern is real — you'll use it for logging agents, proxies (like Envoy in a service mesh), or secret injectors:
spec:
containers:
- name: app
image: my-api:1.0
- name: log-shipper # sidecar
image: fluentd:latest
volumeMounts:
- name: logs
mountPath: /var/log/app
Why it matters practically: K8s schedules pods, not individual containers. Resource requests, limits, and self-healing all operate at the pod level. If a pod crashes, K8s replaces the whole pod — not just the container inside it.
How Docker and K8s Fit Together in a Real Pipeline
They're sequential, not overlapping:
Developer writes code
→ Dockerfile defines the build
→ CI runs: docker build + docker push → registry
→ K8s pulls image from registry
→ K8s schedules pods across nodes
→ K8s manages lifecycle, scaling, health
A typical GitHub Actions pipeline wiring both together:
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build and push image
run: |
docker build -t $REGISTRY/my-api:$GITHUB_SHA .
docker push $REGISTRY/my-api:$GITHUB_SHA
- name: Deploy to K8s
run: |
kubectl set image deployment/my-api \
api=$REGISTRY/my-api:$GITHUB_SHA
kubectl rollout status deployment/my-api
Docker handles the build. Kubernetes handles the deploy. Clean handoff.
Note on runtimes: Kubernetes uses
containerdas its default runtime, not Docker directly. But Docker-built images follow the OCI standard, so they're fully compatible. You don't need to change your build workflow.
When to Use Docker Without Kubernetes ✅
Use Docker alone when:
- You're in early development or pre-PMF
- Your app runs on a single server
- Your team is 1–3 engineers
- You have fewer than ~10 containers total
- You're serving under ~10k DAU
- You're on a managed PaaS (Railway, Fly.io, Cloud Run) that abstracts orchestration for you
- Fast iteration matters more than scaling headroom right now
Docker Compose handles multi-service local dev and even modest production deployments cleanly. Adding K8s at this stage is premature optimization with real engineering cost.
When to Add Kubernetes ✅
Add K8s when:
- You're running 5+ microservices that need coordinated deployment and networking
- Traffic spikes are real and you need auto-scaling
- You have an uptime SLA above 99.9% that requires self-healing
- Multiple teams deploy independently to shared infrastructure
- You need canary releases, blue/green deploys, or sophisticated rollout strategies
- You're serving 100k+ DAU
- Your monthly compute spend is high enough that K8s bin-packing efficiency actually saves money
A rough rule of thumb: if your compute bill is under $5k/month and you have fewer than 15 services, K8s will likely cost more in engineering time than it saves in ops.
When NOT to Use Kubernetes ❌
Avoid K8s when:
- No one on your team has real K8s operational experience
- You're a solo dev or very small startup
- Your stack is a monolith or 2–3 services
- Feature velocity is the priority and K8s YAML would slow you down
- A managed PaaS already gives you what you need
- You're adding it because it looks good on the architecture diagram
I've seen small teams burn weeks configuring ingress controllers, cert-manager, and RBAC for a 3-service app. The overhead is real. Don't add it until the pain of not having orchestration is specific and concrete.
Quick Comparison
| Factor | Docker | Kubernetes |
|---|---|---|
| Primary job | Build + run containers | Orchestrate at scale |
| Scope | Single host | Multi-host cluster |
| Setup time | Minutes | Hours to days |
| Auto-scaling | ❌ | ✅ |
| Self-healing | ❌ | ✅ |
| Rolling updates | Manual | Automated |
| Learning curve | Days | Weeks–months |
| Best for | Dev, small scale | Production, microservices |
Learning Path
Docker learning curve: days.
Kubernetes learning curve: weeks to months.
The progression that actually works:
- Get fluent with
docker build,docker run, image layers, multi-stage builds - Master Docker Compose for local multi-service dev
- Understand container networking (bridge, host, overlay)
- Then introduce K8s concepts: pods → deployments → services → ingress → namespaces → RBAC
- Run locally with
kindorminikubebefore touching production
# Spin up a local K8s cluster for learning
kind create cluster --name dev
kubectl cluster-info --context kind-dev
Managed K8s options when you're ready for production:
| Cloud | Service |
|---|---|
| AWS | EKS |
| GCP | GKE |
| Azure | AKS |
They manage the control plane. You manage your workloads.
Takeaways
- Docker and Kubernetes aren't rivals. They're different layers of the same stack.
- Docker Compose in production is a legitimate, underrated choice for small/medium workloads.
- The Docker → K8s transition is a natural graduation, not a required step.
- Before rolling your own cluster, check if Cloud Run, App Runner, or Container Apps gives you 80% of K8s for 20% of the complexity.
- Master Docker deeply first. The mental models (images, layers, networking, volumes) carry directly into K8s.
- Add K8s when the pain of not having orchestration is specific and real — not theoretical.
Originally published at akoode.com.
Top comments (0)