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
| Command | Purpose |
|---|---|
kompose convert | Convert compose.yaml in current directory |
kompose convert -f file | Convert a specific file |
kompose convert -o dir | Write output to directory |
kompose convert -j | JSON output |
kompose convert --daemon-set | Generate DaemonSets instead of Deployments |
kompose convert -c | Generate Helm chart skeleton |
Object Mapping
| Compose | Kubernetes | Notes |
|---|---|---|
service | Deployment + Service | One of each per service |
image | spec.containers[].image | Must be in a registry |
ports | Service spec.ports | NodePort or ClusterIP |
environment | env (not ConfigMap) | Sensitive values leak |
volumes (named) | PersistentVolumeClaim | Requires StorageClass |
configs | ConfigMap | |
secrets | Secret | Base64, not encrypted |
build: | Not converted | Image must be pre-built |
depends_on | initContainer (partial) | Does not guarantee readiness |
What Kompose Does Not Generate
| Missing Object | Consequence |
|---|---|
| Resource requests/limits | Scheduler cannot place reliably; OOM risk |
| Readiness/liveness probes | Traffic to unready Pods; no restart on failure |
| Ingress/Gateway | No hostname or path routing |
| PodDisruptionBudget | Node drain can kill last replica |
| HorizontalPodAutoscaler | No automatic scaling |
| Proper Secret handling | Sensitive env vars in plain env |
Labels for Customization
| Label | Effect |
|---|---|
kompose.service.type | nodeport, clusterip, loadbalancer |
kompose.service.expose | true 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
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Pods start before database ready | depends_on becomes initContainer but does not wait for readiness | Add app-level retry and readiness probe |
| OOMKilled under load | No memory limits | Add resources.limits.memory |
| Service routes to unready Pod | No readiness probe | Add readinessProbe |
Secret visible in kubectl describe | Environment variable, not secretRef | Use envFrom.secretRef |
| Data lost on Pod restart | Deployment without PVC | Use StatefulSet with volumeClaimTemplates |
| Image pull fails | Built locally, not pushed | Push 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
| Item | Value |
|---|---|
| Tool | kompose, official Kubernetes project |
| Command | kompose convert -f compose.yaml |
| Service mapping | Deployment + Service |
| Volume mapping | PersistentVolumeClaim |
| Config mapping | ConfigMap |
| Secret mapping | Secret (base64, not encrypted) |
| Missing resources | Requests/limits, probes, Ingress, PDB, HPA |
depends_on | initContainer only, no readiness guarantee |
| Production readiness | Requires manual refinement |
Key takeaways:
komposeis 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_ondoes not guarantee readiness, and sensitive environment variables remain in plainenvblocks 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
secretsbecome Kubernetes Secrets, but Composeenvironmentvalues that contain passwords do not. They must be moved to a Secret and referenced withsecretRef. - Databases need StatefulSets, not Deployments. A Deployment without a PVC loses data on restart. The generated
db-deployment.yamlshould be replaced with a StatefulSet andvolumeClaimTemplates. - 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!