惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 聂微东
GbyAI
GbyAI
S
SegmentFault 最新的问题
H
Hackread – Cybersecurity News, Data Breaches, AI and More
V
Visual Studio Blog
WordPress大学
WordPress大学
Hugging Face - Blog
Hugging Face - Blog
B
Blog
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
雷峰网
雷峰网
爱范儿
爱范儿
Vercel News
Vercel News
人人都是产品经理
人人都是产品经理
U
Unit 42
Microsoft Azure Blog
Microsoft Azure Blog
Microsoft Security Blog
Microsoft Security Blog
Jina AI
Jina AI
P
Proofpoint News Feed
A
About on SuperTechFans
I
InfoQ
F
Fortinet All Blogs
L
LangChain Blog
T
Tailwind CSS Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How to Migrate from Docker Compose to Kubernetes: A Pract...
Tiago Luz · 2026-05-29 · via DEV Community

Tiago Luz

Originally published at k8scalc.com


If your app runs on Docker Compose today, Kubernetes is not a rewrite — it's a translation. Every concept in Compose has a direct equivalent in Kubernetes. Once you understand the mapping, the migration becomes mechanical.

This guide walks through migrating a real three-tier app: a Next.js frontend, a PostgreSQL database, and Redis. By the end you'll have production-grade Kubernetes manifests and a clear mental model you can apply to any Compose file.

Concept Mapping

Every Compose primitive has a Kubernetes equivalent. Internalize this table before touching any YAML.

Docker Compose Kubernetes Equivalent Notes
service Deployment + Service Deployment controls pods; Service provides DNS + routing
image spec.containers[].image Same image, same tag
ports Service.spec.ports + Ingress ClusterIP for internal; Ingress for external
environment env or envFrom (ConfigMap/Secret) Never hardcode secrets in pod spec
volumes (named) PersistentVolumeClaim Storage class determines provisioner
volumes (bind mount) hostPath or ConfigMap Avoid hostPath in production
networks NetworkPolicy K8s default is allow-all; policies enforce deny
depends_on Init containers or readiness probes K8s doesn't have native service ordering
healthcheck livenessProbe + readinessProbe More granular than Compose health checks
restart: always restartPolicy: Always (default) Already the default for Deployments
deploy.replicas spec.replicas Same concept, different location
deploy.resources resources.requests + resources.limits K8s requires both for proper scheduling

The Example App

Here's the docker-compose.yml we're migrating:

version: "3.9"
services:
  web:
    image: myapp/frontend:1.4.2
    ports:
      - "3000:3000"
    environment:
      - DATABASE_URL=postgres://app:secret@db:5432/appdb
      - REDIS_URL=redis://cache:6379
    depends_on:
      - db
      - cache
    deploy:
      replicas: 2
      resources:
        limits:
          cpus: "0.5"
          memory: 512M

  db:
    image: postgres:16
    environment:
      - POSTGRES_USER=app
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=appdb
    volumes:
      - pg_data:/var/lib/postgresql/data

  cache:
    image: redis:7-alpine
    volumes:
      - redis_data:/data

volumes:
  pg_data:
  redis_data:

Step 1: Create Namespaces and Secrets

Start with namespace isolation and secrets. Never inline credentials in Deployment specs.

kubectl create namespace myapp

kubectl create secret generic postgres-credentials \
  --namespace myapp \
  --from-literal=POSTGRES_USER=app \
  --from-literal=POSTGRES_PASSWORD=secret \
  --from-literal=POSTGRES_DB=appdb

kubectl create secret generic app-env \
  --namespace myapp \
  --from-literal=DATABASE_URL="postgres://app:secret@db:5432/appdb" \
  --from-literal=REDIS_URL="redis://cache:6379"

Step 2: Persistent Volume Claims

The Compose volumes block becomes PVCs. Each stateful service gets its own claim. Use the Kubernetes PVC Generator to scaffold these quickly.

# postgres-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: postgres-data
  namespace: myapp
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 20Gi
---
# redis-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: redis-data
  namespace: myapp
spec:
  accessModes:
    - ReadWriteOnce
  storageClassName: longhorn
  resources:
    requests:
      storage: 5Gi

Step 3: Deployments

Each Compose service becomes a Deployment. Note how depends_on is replaced with a readinessProbe — Kubernetes will restart the pod and hold traffic until the probe passes.

Use the Kubernetes Deployment Generator to scaffold the base manifests, then add the sections below.

# postgres-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: db
  namespace: myapp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - name: postgres
          image: postgres:16
          envFrom:
            - secretRef:
                name: postgres-credentials
          ports:
            - containerPort: 5432
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "app", "-d", "appdb"]
            initialDelaySeconds: 5
            periodSeconds: 10
          resources:
            requests:
              cpu: "250m"
              memory: "256Mi"
            limits:
              cpu: "1"
              memory: "1Gi"
          volumeMounts:
            - name: pg-data
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: pg-data
          persistentVolumeClaim:
            claimName: postgres-data
---
apiVersion: v1
kind: Service
metadata:
  name: db
  namespace: myapp
spec:
  selector:
    app: db
  ports:
    - port: 5432
      targetPort: 5432

The web service gets an HPA-ready Deployment:

# web-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: myapp
spec:
  replicas: 2
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: frontend
          image: myapp/frontend:1.4.2
          envFrom:
            - secretRef:
                name: app-env
          ports:
            - containerPort: 3000
          readinessProbe:
            httpGet:
              path: /healthz
              port: 3000
            initialDelaySeconds: 10
            periodSeconds: 5
          resources:
            requests:
              cpu: "100m"
              memory: "128Mi"
            limits:
              cpu: "500m"
              memory: "512Mi"
---
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: myapp
spec:
  selector:
    app: web
  ports:
    - port: 3000
      targetPort: 3000

Step 4: Ingress

In Compose, you expose ports directly. In Kubernetes, external traffic flows through an Ingress controller. Use the Kubernetes Ingress Generator to generate the manifest.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: myapp
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - myapp.example.com
      secretName: myapp-tls
  rules:
    - host: myapp.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 3000

Step 5: Network Policies

Docker Compose networks provide implicit isolation between stacks but allow all traffic within a network. In Kubernetes, the default is allow-all across all pods in a cluster. Use the Kubernetes Network Policy Generator to lock this down.

# Deny all ingress to the myapp namespace by default
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: myapp
spec:
  podSelector: {}
  policyTypes:
    - Ingress
---
# Allow web to reach db on 5432
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-web-to-db
  namespace: myapp
spec:
  podSelector:
    matchLabels:
      app: db
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: web
      ports:
        - port: 5432

Common Migration Pitfalls

StatefulSets for databases: Single-replica databases can use Deployment + PVC (as shown above). For clustered Postgres (Patroni, etc.), use a StatefulSet for stable pod identity.

Config drift: Compose environment blocks often accumulate undocumented variables over time. Use this migration as an opportunity to audit every env var and move it to a properly named ConfigMap or Secret.

Image pull policies: Compose always pulls latest by default. In Kubernetes, imagePullPolicy: IfNotPresent is the default for tagged images. Pin your image tags before migrating.

Resource requests: Kubernetes will refuse to schedule pods that exceed node capacity if limits are set. Start with generous requests and tighten after observing actual usage in production.