DEV Community

Cover image for Docker vs Kubernetes: Stop Comparing Them Like They Compete
Mahendra Singh
Mahendra Singh

Posted on • Originally published at akoode.com

Docker vs Kubernetes: Stop Comparing Them Like They Compete

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"]
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:
Enter fullscreen mode Exit fullscreen mode
docker compose up -d     # spin everything up
docker compose logs -f   # follow logs
docker compose down      # tear it all down
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode
kubectl apply -f deployment.yaml
kubectl get pods
kubectl rollout status deployment/my-api
Enter fullscreen mode Exit fullscreen mode

If one of those 3 pods crashes, K8s replaces it automatically. Scale on demand:

kubectl scale deployment my-api --replicas=10
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Docker handles the build. Kubernetes handles the deploy. Clean handoff.

Note on runtimes: Kubernetes uses containerd as 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:

  1. Get fluent with docker build, docker run, image layers, multi-stage builds
  2. Master Docker Compose for local multi-service dev
  3. Understand container networking (bridge, host, overlay)
  4. Then introduce K8s concepts: pods → deployments → services → ingress → namespaces → RBAC
  5. Run locally with kind or minikube before touching production
# Spin up a local K8s cluster for learning
kind create cluster --name dev
kubectl cluster-info --context kind-dev
Enter fullscreen mode Exit fullscreen mode

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)