import ComparisonTable from ’../../components/ComparisonTable.astro’;
Docker Compose and Kubernetes solve the same fundamental problem — running multiple containers together — but at completely different scales of complexity and capability. The choice between them is less about “which is better” and more about “which is appropriate for your situation.”
Quick Verdict
Use Docker Compose if: Local development, small deployments, single-host environments, simple apps, or teams where Kubernetes operational overhead isn’t justified.
Use Kubernetes if: Production multi-node clusters, auto-scaling requirements, high availability, large teams, or when you need the full container orchestration feature set.
Feature Comparison
<ComparisonTable headers={[“Feature”, “Docker Compose”, “Kubernetes”]} rows={[ [“Primary use case”, “Local dev / simple deploys”, “Production orchestration”], [“Multi-node support”, “No (single host)”, “Yes (cluster)”], [“Auto-scaling”, “No”, “Yes (HPA, VPA, KEDA)”], [“Self-healing”, “Basic (restart policies)”, “Full (pod rescheduling)”], [“Load balancing”, “No (external needed)”, “Native (Services, Ingress)”], [“Rolling updates”, “Manual”, “Native (Deployments)”], [“Config management”, “Environment variables”, “ConfigMaps, Secrets”], [“Service discovery”, “DNS (compose network)”, “DNS + kube-proxy”], [“Storage”, “Volumes (simple)”, “PV, PVC, StorageClasses”], [“Networking”, “Bridge networks”, “CNI plugins (Calico, Cilium)”], [“RBAC”, “None”, “Full RBAC”], [“Learning curve”, “Low (hours)”, “High (weeks-months)”], [“Operational overhead”, “Minimal”, “Significant”], [“Managed cloud option”, “No”, “EKS, GKE, AKS”], ]} />
Docker Compose
Docker Compose is a tool for defining and running multi-container Docker applications with a single YAML file:
Basic docker-compose.yml:
version: "3.9"
services:
web:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- DATABASE_URL=postgresql://postgres:password@db:5432/myapp
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:15
environment:
POSTGRES_DB: myapp
POSTGRES_USER: postgres
POSTGRES_PASSWORD: password
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
volumes:
- redis_data:/data
command: redis-server --appendonly yes
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/ssl:ro
depends_on:
- web
volumes:
postgres_data:
redis_data:
Running Docker Compose:
# Start all services
docker compose up -d
# View logs
docker compose logs -f web
# Scale a service (limited — single host only)
docker compose up -d --scale web=3
# Run one-off commands
docker compose exec web npm run migrate
# Update and redeploy
docker compose pull && docker compose up -d
# Tear down
docker compose down -v # -v removes volumes
Docker Compose profiles (for dev/test environments):
services:
web:
build: .
profiles: ["app"]
db:
image: postgres:15
profiles: ["app"]
# Only in dev environment
adminer:
image: adminer
ports:
- "8080:8080"
profiles: ["dev"]
# Only for testing
test-db:
image: postgres:15
environment:
POSTGRES_DB: test
profiles: ["test"]
# Run app + dev tools
docker compose --profile dev up -d
# Run app + test database
docker compose --profile app --profile test up -d
Kubernetes
Kubernetes is a full container orchestration platform designed for running workloads across multiple nodes with high availability:
Equivalent application in Kubernetes:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: myapp
spec:
replicas: 3
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: myapp/web:1.2.3
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
- name: DATABASE_URL
valueFrom:
secretKeyRef:
name: db-secret
key: url
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
---
# service.yaml
apiVersion: v1
kind: Service
metadata:
name: web
namespace: myapp
spec:
selector:
app: web
ports:
- port: 80
targetPort: 3000
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
namespace: myapp
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
tls:
- hosts:
- myapp.com
secretName: myapp-tls
rules:
- host: myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
StatefulSet for databases:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres
namespace: myapp
spec:
serviceName: postgres
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:15
env:
- name: POSTGRES_DB
value: myapp
- name: POSTGRES_USER
valueFrom:
secretKeyRef:
name: db-secret
key: username
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
volumeClaimTemplates:
- metadata:
name: postgres-data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: gp3
resources:
requests:
storage: 20Gi
Auto-Scaling
This is where Kubernetes pulls far ahead of Docker Compose:
Horizontal Pod Autoscaler (HPA):
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
namespace: myapp
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
KEDA (event-driven scaling):
# Scale based on queue depth
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: worker-scaler
spec:
scaleTargetRef:
name: worker
minReplicaCount: 0 # Scale to zero when no work
maxReplicaCount: 50
triggers:
- type: rabbitmq
metadata:
queueName: job-queue
queueLength: "10" # 1 pod per 10 queue items
Docker Compose has no equivalent — you’d manually edit replicas or use a separate scaling script.
Networking
Docker Compose networking:
services:
web:
networks:
- frontend
- backend
api:
networks:
- backend
db:
networks:
- backend
networks:
frontend:
backend:
internal: true # Not accessible from outside
Services find each other by name: http://api:3000, postgresql://db:5432.
Kubernetes networking:
# Services provide stable DNS + load balancing
# web → http://web.myapp.svc.cluster.local:80
# api → http://api.myapp.svc.cluster.local:3000
# NetworkPolicy: restrict traffic between pods
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-policy
namespace: myapp
spec:
podSelector:
matchLabels:
app: postgres
ingress:
- from:
- podSelector:
matchLabels:
app: api # Only api pods can reach postgres
ports:
- port: 5432
The “Both Together” Pattern
Many teams use both: Docker Compose for local development, Kubernetes for production:
Local development: docker-compose.yml
- Fast startup
- All services on one machine
- Volume mounts for hot reload
- Simple to reset and rebuild
Production: Kubernetes manifests (or Helm charts)
- Multi-node cluster
- Auto-scaling
- High availability
- GitOps with ArgoCD/Flux
Bridge: Skaffold or Tilt
- Inner loop development against local cluster
- Hot reload in Kubernetes dev cluster
- Same manifests, different config
Managed Kubernetes vs. Self-Hosted
If you choose Kubernetes, the next decision is managed vs. self-hosted:
| Option | Managed | Self-Hosted |
|---|---|---|
| Control plane | Cloud-managed | You manage |
| Upgrade process | Automated | Manual |
| Cost | +$70-150/cluster/month | Your infra cost |
| Operational burden | Low | High |
| Options | EKS, GKE, AKS | kubeadm, k3s, RKE2 |
For most teams: use managed Kubernetes (EKS/GKE/AKS). Self-hosting the control plane is only worth it for specific compliance requirements or cost optimization at large scale.
When to Choose Each
Choose Docker Compose:
- Local development (always — even if Kubernetes in prod)
- Single-server deployments (VPS, DigitalOcean Droplet)
- Small internal tools and side projects
- Teams without Kubernetes expertise
- When simplicity and fast iteration matter more than scale
Choose Kubernetes:
- Production workloads requiring HA and multi-node
- Apps that need auto-scaling based on traffic
- Multi-team environments with different services
- Regulated environments needing RBAC and network policies
- When you’re already using cloud-managed Kubernetes
Bottom Line
Docker Compose is the right tool for what it is: a simple, fast way to run multi-container applications on a single host. It’s not a stepping stone to Kubernetes — it’s a valid production tool for many workloads. Kubernetes is the right tool when you genuinely need what it provides: multi-node clustering, auto-scaling, and production-grade orchestration. The most common mistake is using Kubernetes for workloads that Docker Compose would serve better — adding complexity without commensurate benefit. Start with Docker Compose; graduate to Kubernetes when your workload demands it.