| |

Docker 69 🐳 Translating Compose Manifests to Kubernetes Objects using kompose

kompose is the official Kubernetes project for converting Docker Compose files into Kubernetes or OpenShift manifests . Its purpose is mechanical translation: it reads the services, volumes, and port definitions in a compose.yaml file and produces Deployments, Services, and PersistentVolumeClaims that the Kubernetes API can accept . The tool is explicitly designed as a starting point, not a production-ready generator. The Kompose documentation itself describes the conversion as covering “99% of the way there” but acknowledges that the remaining 1%—resource limits, health probes, proper secret handling, and ingress configuration—must be added by hand .

This chapter covers the installation and invocation of kompose, the object mapping it performs, the conversion losses that matter for production, and the manual refinements that turn the generated YAML into a deployable application. The focus is on what the tool does correctly, what it silently omits, and how to recognize the difference before applying the output to a live cluster.

Key point: kompose convert translates each Compose service into a Deployment and a Service, named volumes into PersistentVolumeClaims, and configs/secrets into ConfigMaps/Secrets; it does not generate resource limits, health probes, Ingress objects, or production-grade Secret handling, all of which must be added manually after conversion.


Why kompose exists and what it assumes

The mechanical translation problem. A Compose file describes services, networks, volumes, and dependencies in a single YAML document. A Kubernetes deployment requires multiple API objects, each with its own schema, metadata, and lifecycle. Writing those objects by hand for every service is tedious and error-prone, especially for the first migration. kompose automates the structural translation: it reads the Compose model and emits the corresponding Kubernetes objects with the correct apiVersion, kind, and basic spec fields .

The single-host to cluster assumption. Compose assumes a single Docker Engine. Kubernetes assumes a cluster with a control plane and multiple nodes. kompose bridges these assumptions by translating Compose services into Deployments with a default replica count of 1, and port mappings into Services with ClusterIP or NodePort types depending on the Compose port syntax . The tool does not attempt to model the cluster’s scheduling behavior, node affinity, or resource capacity. It produces a valid object graph; it does not produce a cluster-aware deployment.

The explicit limitation. The Kubernetes documentation describes kompose as a tool that “takes a Compose file and translates it into Kubernetes resources,” and it includes a conversion matrix that documents exactly which Compose keys are supported, partially supported, or ignored . This transparency is the tool’s most important feature. It does not claim to be a complete migration solution, and the documentation makes the gaps visible.


a. Installation and basic conversion

kompose is distributed as a single binary through GitHub releases. The installation is a download and a chmod:

# Linux
curl -L https://github.com/kubernetes/kompose/releases/download/v1.34.0/kompose-linux-amd64 -o kompose
chmod +x kompose
sudo mv ./kompose /usr/local/bin/kompose

On macOS, Homebrew provides the package: brew install kompose . On Windows, the release page provides an .exe binary.

The conversion command reads a Compose file and writes YAML files to the current directory:

kompose convert -f compose.yaml

The output lists each file created. A typical conversion of a web service and a Redis leader produces web-deployment.yaml, web-service.yaml, redis-leader-deployment.yaml, and redis-leader-service.yaml . The tool also generates PersistentVolumeClaim files for named volumes, with names like mongodb-claim0-persistentvolumeclaim.yaml .

The -o flag directs the output to a specific directory, which keeps the generated files separate from the Compose source:

kompose convert -f compose.yaml -o ./k8s-manifests

b. The object mapping and its losses

The conversion follows a consistent mapping. A Compose service becomes a Kubernetes Deployment and a Service . The image reference, environment variables, and port definitions are carried over. Named volumes become PersistentVolumeClaims. Compose configs become ConfigMaps, and Compose secrets become Kubernetes Secrets .

The losses are equally consistent, and they are the parts that matter in production.

No resource requests or limits. The generated Deployments have no resources.requests or resources.limits blocks. Without requests, the Kubernetes scheduler cannot make informed placement decisions. Without limits, a Pod that consumes excessive memory can trigger the node’s OOM killer and disrupt other workloads . The practical consequence is that a Kompose-generated Deployment is schedulable but not reliable. The fix is to add explicit requests and limits that match the application’s measured usage.

No health probes. The generated Deployments have no readinessProbe or livenessProbe. Without a readiness probe, the Service routes traffic to a Pod as soon as the container starts, regardless of whether the application inside is ready to handle requests. Without a liveness probe, a deadlocked or unresponsive container is never restarted . The fix is to add an HTTP or TCP probe that checks the application’s health endpoint, with initialDelaySeconds tuned to the application’s startup time.

No Ingress or Gateway. Compose port mappings become Kubernetes Service objects of type NodePort or ClusterIP, depending on the syntax. They do not become Ingress resources. For HTTP routing with hostnames and paths, an Ingress controller must be installed and an Ingress object must be written by hand . The generated Service is reachable, but only through a node port or cluster-internal IP, not through a domain name.

Secrets are not production-grade. Compose secrets become Kubernetes Secret objects, but Kubernetes Secrets are base64-encoded, not encrypted. Anyone with access to the namespace can decode them. The Kompose documentation notes that Secret conversion is supported, but the operational security of those Secrets is the responsibility of the cluster operator, not the conversion tool .

No PodDisruptionBudget or HorizontalPodAutoscaler. These objects control availability during node maintenance and scale replicas under load. kompose does not generate them, and without them, a node drain can take down the last replica of a service, and traffic spikes are handled by static replica counts .

The depends_on translation is incomplete. Kompose may generate an initContainer for a depends_on dependency, but it does not know how long the dependency takes to become ready. A PostgreSQL container may start in seconds but not accept connections for another twenty seconds. The init container that checks nc -z postgres 5432 will pass as soon as the port is open, even if the database is not ready to serve queries . The application must also implement retry logic with exponential backoff, and the readiness probe must verify the database connection before the Pod is added to the Service endpoints.


c. Refining the generated manifests

The refinement process adds what Kompose omits. The first addition is resource requests and limits. The values depend on the application, but the structure is standard:

resources:
  requests:
    memory: "128Mi"
    cpu: "100m"
  limits:
    memory: "256Mi"
    cpu: "500m"

The second addition is probes. A readiness probe with httpGet on a /ready endpoint prevents traffic from reaching the Pod until the application is initialized. A liveness probe on /health triggers a restart if the application stops responding:

readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 5
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 10

The third addition is Secret handling. Environment variables that contain passwords or tokens should not be in the Deployment’s env block. They should be in a Secret object and referenced through envFrom or valueFrom.secretKeyRef:

envFrom:
  - secretRef:
      name: app-secrets

The fourth addition is the Ingress. If the application needs to be reachable by hostname, an Ingress object must be created with the host and path rules, and an Ingress controller (Nginx, Traefik, or the cloud provider’s controller) must be installed in the cluster.

The fifth addition is the depends_on replacement. The init container waits for the port to be open. The application’s connection logic must retry with backoff. The readiness probe must verify the dependency is actually usable, not just reachable.


Complete Example Session

The following session converts a two-service Compose file with a named volume and a secret, inspects the generated manifests, and adds the production refinements.

# ============================================
# PART 1: THE COMPOSE FILE
# ============================================
# compose.yaml
services:
  api:
    image: registry.example.com/api:v1.2.3
    ports:
      - "8080:8080"
    environment:
      - DATABASE_URL=postgres://user:pass@db:5432/app
    depends_on:
      - db
  db:
    image: postgres:16
    environment:
      - POSTGRES_PASSWORD=secret
    volumes:
      - db_data:/var/lib/postgresql/data

volumes:
  db_data:
# ============================================
# PART 2: CONVERT WITH KOMPOSE
# ============================================
kompose convert -f compose.yaml -o ./k8s-manifests

# Output:
# INFO Kubernetes file "api-service.yaml" created
# INFO Kubernetes file "api-deployment.yaml" created
# INFO Kubernetes file "db-service.yaml" created
# INFO Kubernetes file "db-deployment.yaml" created
# INFO Kubernetes file "db-data-persistentvolumeclaim.yaml" created
# ============================================
# PART 3: THE GENERATED API DEPLOYMENT (INSPECTED)
# ============================================
# k8s-manifests/api-deployment.yaml (abridged)
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 1
  template:
    spec:
      containers:
        - name: api
          image: registry.example.com/api:v1.2.3
          env:
            - name: DATABASE_URL
              value: "postgres://user:pass@db:5432/app"
          ports:
            - containerPort: 8080
# Missing: resources, probes, secretRef
# ============================================
# PART 4: REFINED API DEPLOYMENT
# ============================================
# Add initContainer for db wait, resources, probes, secretRef
apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 2
  template:
    spec:
      initContainers:
        - name: wait-for-db
          image: busybox:1.36
          command: ['sh', '-c', 'until nc -z db 5432; do sleep 2; done']
      containers:
        - name: api
          image: registry.example.com/api:v1.2.3
          envFrom:
            - secretRef:
                name: api-secrets
          ports:
            - containerPort: 8080
          resources:
            requests:
              memory: "128Mi"
              cpu: "100m"
            limits:
              memory: "256Mi"
              cpu: "500m"
          readinessProbe:
            httpGet:
              path: /ready
              port: 8080
            initialDelaySeconds: 5
          livenessProbe:
            httpGet:
              path: /health
              port: 8080
            initialDelaySeconds: 10
# ============================================
# PART 5: THE SECRET (HAND-CREATED)
# ============================================
apiVersion: v1
kind: Secret
metadata:
  name: api-secrets
type: Opaque
stringData:
  DATABASE_URL: "postgres://user:pass@db:5432/app"
# ============================================
# PART 6: THE DB DEPLOYMENT (INSPECTED)
# ============================================
# The generated db-deployment.yaml has:
# - No resources
# - No probes
# - A volumeMount referencing the PVC
# - The password in an env block, not a Secret
# ============================================
# PART 7: REFINED DB DEPLOYMENT
# ============================================
# Change to a StatefulSet for stable storage
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: db
spec:
  serviceName: db
  replicas: 1
  template:
    spec:
      containers:
        - name: postgres
          image: postgres:16
          env:
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: db-secrets
                  key: password
          volumeMounts:
            - name: data
              mountPath: /var/lib/postgresql/data
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 10Gi
# ============================================
# PART 8: APPLY THE REFINED MANIFESTS
# ============================================
kubectl apply -f k8s-manifests/api-deployment.yaml
kubectl apply -f k8s-manifests/api-service.yaml
kubectl apply -f k8s-manifests/api-secrets.yaml
kubectl apply -f k8s-manifests/db-statefulset.yaml
kubectl apply -f k8s-manifests/db-secrets.yaml
kubectl apply -f k8s-manifests/db-service.yaml

The eight parts show the Compose source, the Kompose conversion, the generated Deployment with its omissions visible, the refined Deployment with init container, resources, probes, and secretRef, the hand-created Secret, the generated database Deployment with the same omissions, the refined StatefulSet, and the application commands. What the session demonstrates is that Kompose produces the skeleton, but the operational configuration—the parts that determine whether the application survives load and failure—must be written by hand.


Quick Reference

Kompose Commands

CommandPurpose
kompose convertConvert compose.yaml in current directory
kompose convert -f fileConvert a specific file
kompose convert -o dirWrite output to directory
kompose convert -jJSON output
kompose convert --daemon-setGenerate DaemonSets instead of Deployments
kompose convert -cGenerate Helm chart skeleton

Object Mapping

ComposeKubernetesNotes
serviceDeployment + ServiceOne of each per service
imagespec.containers[].imageMust be in a registry
portsService spec.portsNodePort or ClusterIP
environmentenv (not ConfigMap)Sensitive values leak
volumes (named)PersistentVolumeClaimRequires StorageClass
configsConfigMap
secretsSecretBase64, not encrypted
build:Not convertedImage must be pre-built
depends_oninitContainer (partial)Does not guarantee readiness

What Kompose Does Not Generate

Missing ObjectConsequence
Resource requests/limitsScheduler cannot place reliably; OOM risk
Readiness/liveness probesTraffic to unready Pods; no restart on failure
Ingress/GatewayNo hostname or path routing
PodDisruptionBudgetNode drain can kill last replica
HorizontalPodAutoscalerNo automatic scaling
Proper Secret handlingSensitive env vars in plain env

Labels for Customization

LabelEffect
kompose.service.typenodeport, clusterip, loadbalancer
kompose.service.exposetrue or hostname; creates Ingress

Best Practices

✅ Do This:

kompose convert -f compose.yaml -o ./k8s-manifests    # ✅ separate output
kubectl apply --dry-run=client -f ./k8s-manifests     # ✅ validate before apply
# add resources.requests and resources.limits         # ✅ after conversion
# add readinessProbe and livenessProbe                 # ✅ after conversion
# move sensitive env vars to Secret + secretRef        # ✅ after conversion

❌ Don’t Do This:

kubectl apply -f .                                    # ❌ no review
# trust Kompose output as production-ready           # ❌ missing critical fields
# leave DATABASE_URL in env value                    # ❌ leaks in describe
# use Deployment for PostgreSQL without PVC          # ❌ data lost on restart

Common Pitfalls

PitfallWhy It HappensFix
Pods start before database readydepends_on becomes initContainer but does not wait for readinessAdd app-level retry and readiness probe
OOMKilled under loadNo memory limitsAdd resources.limits.memory
Service routes to unready PodNo readiness probeAdd readinessProbe
Secret visible in kubectl describeEnvironment variable, not secretRefUse envFrom.secretRef
Data lost on Pod restartDeployment without PVCUse StatefulSet with volumeClaimTemplates
Image pull failsBuilt locally, not pushedPush to registry before conversion

Real-World Examples

1. Basic conversion

kompose convert -f compose.yaml

2. Output to directory

kompose convert -f compose.yaml -o ./k8s

3. Generate Helm chart

kompose convert -c

4. Service type label

labels:
  kompose.service.type: LoadBalancer

5. Expose with Ingress

labels:
  kompose.service.expose: "app.example.com"

6. Validate before apply

kubectl apply --dry-run=client -f ./k8s/

7. Add resource limits

resources:
  limits:
    memory: "256Mi"

8. Add readiness probe

readinessProbe:
  httpGet: { path: /ready, port: 8080 }

9. Secret reference

envFrom:
  - secretRef: { name: app-secrets }

10. Check conversion matrix

https://kompose.io/conversion/

Visual

Conversion Flow

┌──────────────────────────────────────────────┐
│  compose.yaml                                │
│  ┌────────────────────────────────────────┐  │
│  │ services: api, db                      │  │
│  │ volumes: db_data                       │  │
│  └────────────────────────────────────────┘  │
│         │                                    │
│         ▼                                    │
│  kompose convert                             │
│         │                                    │
│         ▼                                    │
│  ┌────────────────────────────────────────┐  │
│  │ api-deployment.yaml                    │  │
│  │ api-service.yaml                       │  │
│  │ db-deployment.yaml                     │  │
│  │ db-service.yaml                        │  │
│  │ db-data-pvc.yaml                       │  │
│  └────────────────────────────────────────┘  │
│         │                                    │
│         ▼                                    │
│  MANUAL REFINEMENT                           │
│  - resources                                 │
│  - probes                                    │
│  - secrets                                   │
│  - statefulset for db                        │
│         │                                    │
│         ▼                                    │
│  kubectl apply                               │
└──────────────────────────────────────────────┘

What Kompose Generates vs What It Omits

┌──────────────────────────────────────────────┐
│  GENERATED                                   │
│  ✅ Deployment (image, replicas, ports)      │
│  ✅ Service (selector, port)                 │
│  ✅ PersistentVolumeClaim (named volumes)    │
│  ✅ ConfigMap (from configs)                 │
│  ✅ Secret (from secrets, base64 only)       │
│                                              │
│  OMITTED                                     │
│  ❌ Resource requests/limits                 │
│  ❌ Readiness/liveness probes                │
│  ❌ Ingress/Gateway                          │
│  ❌ PodDisruptionBudget                      │
│  ❌ HorizontalPodAutoscaler                  │
│  ❌ Proper Secret separation from env         │
└──────────────────────────────────────────────┘

depends_on Translation Gap

┌──────────────────────────────────────────────┐
│  COMPOSE: depends_on: [db]                   │
│         │                                    │
│         ▼                                    │
│  KOMPOSE: initContainer wait-for-db          │
│  until nc -z db 5432; do sleep 2; done      │
│         │                                    │
│         ▼                                    │
│  PROBLEM: port open ≠ database ready         │
│  PostgreSQL accepts connections before      │
│  it can serve queries                        │
│         │                                    │
│         ▼                                    │
│  FIX: app retry + readinessProbe on /ready   │
└──────────────────────────────────────────────┘

Manual Refinement Checklist

┌──────────────────────────────────────────────┐
│  After kompose convert:                      │
│                                              │
│  1. Add resources.requests and limits        │
│  2. Add readinessProbe and livenessProbe     │
│  3. Move sensitive env to Secret             │
│  4. Convert DB Deployment to StatefulSet     │
│  5. Add Ingress if hostname routing needed   │
│  6. Add app retry for depends_on             │
│  7. Validate with kubectl --dry-run          │
└──────────────────────────────────────────────┘

Summary

ItemValue
Toolkompose, official Kubernetes project
Commandkompose convert -f compose.yaml
Service mappingDeployment + Service
Volume mappingPersistentVolumeClaim
Config mappingConfigMap
Secret mappingSecret (base64, not encrypted)
Missing resourcesRequests/limits, probes, Ingress, PDB, HPA
depends_oninitContainer only, no readiness guarantee
Production readinessRequires manual refinement

Key takeaways:

  • kompose is a structural translator, not a production generator. It produces valid Deployments and Services, but omits the resource limits, probes, and Secret handling that determine reliability .
  • The conversion is lossy in ways that matter. build: is not converted, depends_on does not guarantee readiness, and sensitive environment variables remain in plain env blocks unless moved manually .
  • Resource limits are the first thing to add. Without them, the scheduler cannot place Pods reliably, and a single leaking container can destabilize the node .
  • Readiness probes prevent traffic to unready Pods. The generated Service routes to any running Pod, regardless of whether the application inside is initialized .
  • Secrets need manual separation. Compose secrets become Kubernetes Secrets, but Compose environment values that contain passwords do not. They must be moved to a Secret and referenced with secretRef .
  • Databases need StatefulSets, not Deployments. A Deployment without a PVC loses data on restart. The generated db-deployment.yaml should be replaced with a StatefulSet and volumeClaimTemplates .
  • The output is a starting point for review. Treat the generated YAML as a draft, validate it with kubectl --dry-run, and refine it against the application’s actual resource usage and health endpoints .

Remember: kompose automates the tedious part of the Compose-to-Kubernetes migration—the naming, the API versions, the basic structure—but it cannot automate the operational judgment. It does not know how much memory your application needs, what endpoint indicates readiness, or which environment variables are secrets. The tool’s value is that it makes the translation repeatable and reviewable, so that the manual refinements can be applied consistently. The generated manifests are the skeleton; the production configuration is the work that follows.



Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!