← back to blog
devops·Aug 28, 2025·9 min read

From Docker Compose to Kubernetes: A Production Migration Guide

Migrating workloads from Docker Compose to Kubernetes — translating services to Deployments, handling secrets and ConfigMaps, setting up Ingress, health checks, resource limits, and HPA for auto-scaling.

SJ
Sabin Joshi
DevOps Engineer
#docker#kubernetes#devops#migration#helm#ingress#hpa

When to Make the Jump

Docker Compose is excellent for local development and simple single-host deployments. But it has hard limits: no built-in high availability, no auto-scaling, manual rolling updates, and limited health check orchestration. If you're running more than 3–4 services in production and need reliability, Kubernetes is the right next step.

ℹ️Don't migrate to Kubernetes because it's fashionable. Migrate when you need: multi-host deployment, automated failover, auto-scaling, or more sophisticated rolling update strategies.

Compose → Kubernetes Mapping

Docker Compose to Kubernetes Concept Mapping
{arr('a','#555')}{arr('ag','#00ff88')} Docker Compose Kubernetes service Deployment + Service environment: variables ConfigMap + env: secrets: Secret + envFrom: volumes: PersistentVolumeClaim ports: "80:80" Ingress + Service deploy.replicas: 2 Deployment.replicas + HPA

Translating a Service

# docker-compose.yml (before)
services:
  api:
    image: myorg/api:latest
    ports: ["3000:3000"]
    environment:
      DATABASE_URL: postgres://db:5432/app
    secrets: [db_password]
    deploy:
      replicas: 2
      resources:
        limits: { cpus: '0.5', memory: 512M }

---
# kubernetes/deployment.yaml (after)
apiVersion: apps/v1
kind: Deployment
metadata: { name: api }
spec:
  replicas: 2
  selector: { matchLabels: { app: api } }
  template:
    metadata: { labels: { app: api } }
    spec:
      containers:
      - name: api
        image: myorg/api:1.2.3  # pinned tag, not latest!
        ports: [{ containerPort: 3000 }]
        env:
        - { name: DATABASE_URL, value: "postgres://db:5432/app" }
        envFrom:
        - secretRef: { name: api-secrets }
        resources:
          limits:   { cpu: 500m, memory: 512Mi }
          requests: { cpu: 100m, memory: 128Mi }  # always set requests!
        livenessProbe:
          httpGet: { path: /health, port: 3000 }
          initialDelaySeconds: 10
        readinessProbe:
          httpGet: { path: /ready, port: 3000 }
          initialDelaySeconds: 5

Horizontal Pod Autoscaler

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata: { name: api-hpa }
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: api
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target: { type: Utilization, averageUtilization: 70 }

Use Helm for Packaging

Once you have more than 3–4 YAML files per service, Helm charts become essential. They let you template environment-specific values, manage upgrades with rollback, and share service patterns across teams. Use helm create to bootstrap a chart from your existing YAML.

⚠️Never use image: latest in Kubernetes. Always pin to a specific tag or digest. imagePullPolicy: Always with latest means your deployment can change behavior between pod restarts — this has caused production incidents.