Chapter 66: Docker and Kubernetes Together: Dockerfile to Registry to Cluster, Jenkins and Kompose
66.5 Kompose: convert docker-compose.yml to Kubernetes and fix what it misses
Why: the problem
Many teams arrive at Kubernetes with a working docker-compose.yml: an app, a database, maybe Redis, all started with one docker compose up. Kompose is a CNCF tool that reads that file and writes Kubernetes manifests: a Deployment and Service per service, PersistentVolumeClaims for named volumes, and ConfigMaps for env files if you ask. It turns an afternoon of typing into ten seconds, which makes it a great starting point. It is only a starting point. Compose describes one machine; Kubernetes describes a cluster, and some ideas do not translate. depends_on has no equivalent, because Kubernetes expects apps to retry until dependencies are ready. Compose healthchecks become probes only partly. Environment values are copied in plain text, including passwords. Images without a registry prefix will not be pullable. A Postgres service becomes a Deployment with a PVC, when a database should be a StatefulSet or, better, a managed service like Amazon RDS. Resource limits, securityContext and readiness probes are usually missing. Ports that were published for your laptop become Services of type ClusterIP or, with labels, NodePort or LoadBalancer. Kompose understands extra labels in the compose file, such as kompose.service.type, kompose.service.expose for an Ingress host, and kompose.image-pull-policy, so you can steer the output without editing YAML afterwards. The current release is 1.38.0 (January 2026); install it as a single binary. In this lesson we write a compose file for orders plus Postgres and Redis with printf, run it locally, convert it, read what Kompose produced, then fix every gap: Secret instead of plain env, StatefulSet for Postgres, probes, resources, securityContext and the registry image name. The end result is close to what you built by hand in 66.3, with a clear checklist you can reuse.
What: every term in plain English
- Kompose — CLI that converts Compose files to Kubernetes or OpenShift manifests.
- kompose convert — Writes YAML files, one per object, into a folder.
- depends_on — Compose start order; no Kubernetes equivalent.
- Named volume — Compose volume that Kompose turns into a PVC.
- kompose.service.type — Label that sets the Service type.
- kompose.service.expose — Label that creates an Ingress for a host.
- StatefulSet — Controller for stable identity and storage, used for databases.
- initContainer — Runs before the app; can wait for a dependency.
- --with-kompose-annotation — Adds or removes kompose metadata annotations in output.
- Post-conversion checklist — Images, secrets, probes, resources, security, storage, exposure.
How: step by step
- Write docker-compose.yml with printf. Three services.
- Run it locally. Works on Docker.
- Install Kompose and convert. Manifests created.
- Read the output. Find the gaps.
- Move passwords to a Secret. No plain text.
- Replace the Postgres Deployment with a StatefulSet. Stable storage.
- Add probes, resources and securityContext. Hardened.
- Apply to kind and test. Running.
Keep the post-conversion checklist short enough to remember: images, secrets, probes, resources, security, storage, exposure. Images must have a registry prefix and an immutable tag. Secrets must come from Secret objects with values supplied outside Git. Every long-running container needs readiness and liveness probes. Every container needs requests and a memory limit. Every pod should run as non-root with no privilege escalation. Every stateful service needs a StatefulSet or, better, a managed service. Every public service needs an Ingress or Gateway route with TLS from ch65. Teams migrating dozens of compose stacks at companies like Meesho and Dream11 run this checklist as a review template, and often add a policy engine such as Kyverno so the cluster itself rejects manifests that skip probes or run as root.
Example 1: A compose file for orders, Postgres and Redis
Goal. A compose file for orders, Postgres and Redis.
compose.sh
cd orders-app
printf '%s\n' \
'services:' \
' orders:' \
' image: localhost:5001/orders:1.0.0-a1b2c3d' \
' ports: ["8080:8080"]' \
' environment:' \
' APP_ENV: compose' \
' DATABASE_URL: postgresql://orders:change-me@db:5432/orders' \
' REDIS_URL: redis://cache:6379/0' \
' depends_on: [db, cache]' \
' labels:' \
' kompose.service.type: clusterip' \
' kompose.service.expose: orders.shop.example.com' \
' db:' \
' image: postgres:17' \
' environment:' \
' POSTGRES_USER: orders' \
' POSTGRES_PASSWORD: change-me' \
' POSTGRES_DB: orders' \
' volumes: ["pgdata:/var/lib/postgresql/data"]' \
' ports: ["5432"]' \
' cache:' \
' image: redis:7-alpine' \
' ports: ["6379"]' \
'volumes:' \
' pgdata: {}' > docker-compose.yml
docker compose up -d
docker compose ps
curl -s localhost:8080/healthz
What each line does
change-me— Placeholder password, kept only to show the problem.depends_on— Start order on one machine.kompose labels— Tell Kompose to make a ClusterIP Service and an Ingress.ports 5432 and 6379— Container ports only, so Kompose creates Services for db and cache.pgdata— Named volume, becomes a PVC.
Green output
$ bash compose.sh
[+] Running 4/4
✔ Volume "orders-app_pgdata" Created
✔ Container orders-app-db-1 Started
✔ Container orders-app-cache-1 Started
✔ Container orders-app-orders-1 Started
NAME SERVICE STATUS PORTS
orders-app-orders-1 orders Up 0.0.0.0:8080->8080/tcp
orders-app-db-1 db Up 5432/tcp
orders-app-cache-1 cache Up 6379/tcp
ok
Expected result. The three-service app works on one machine; now we move it to the cluster.
Example 2: Convert with Kompose and read what it produced
Goal. Convert with Kompose and read what it produced.
convert.sh
KOMPOSE_VERSION=v1.38.0
go install github.com/kubernetes/kompose@$KOMPOSE_VERSION || brew install kompose
install -m 0755 "$(go env GOPATH)/bin/kompose" ~/.local/bin/kompose
kompose version
mkdir -p k8s && kompose convert -f docker-compose.yml -o k8s/
ls k8s
grep -n "POSTGRES_PASSWORD" -A1 k8s/db-deployment.yaml
grep -n "Probe\|resources\|securityContext" k8s/orders-deployment.yaml || printf 'no probes, resources or securityContext\n'
grep -n "kind:" k8s/db-deployment.yaml
What each line does
install -m 0755— Executable for you, not world-writable.go install ...@v1.38.0— Builds the pinned release; the official install page at kompose.io/installation also lists the release binaries, brew and dnf.kompose convert -o— Writes one file per object.grep checks— Show the three classic gaps: plain password, no probes, Deployment for a database.orders-ingress.yaml— Created from kompose.service.expose.
Green output
$ bash convert.sh
1.38.0 (a8f5d1c)
INFO Kubernetes file "k8s/orders-service.yaml" created
INFO Kubernetes file "k8s/db-service.yaml" created
INFO Kubernetes file "k8s/cache-service.yaml" created
INFO Kubernetes file "k8s/orders-deployment.yaml" created
INFO Kubernetes file "k8s/orders-ingress.yaml" created
INFO Kubernetes file "k8s/db-deployment.yaml" created
INFO Kubernetes file "k8s/pgdata-persistentvolumeclaim.yaml" created
INFO Kubernetes file "k8s/cache-deployment.yaml" created
32: - name: POSTGRES_PASSWORD
33: value: change-me
no probes, resources or securityContext
2:kind: Deployment
Expected result. Kompose did the bulk of the typing but left a plain-text password, no probes, no limits, no hardening, and a Deployment for Postgres.
Example 3: Fix the gaps and apply to kind
Goal. Fix the gaps and apply to kind.
fix.sh
kubectl create namespace shop
kubectl -n shop create secret generic db-secret --from-literal=POSTGRES_PASSWORD=change-me-placeholder \
--from-literal=DATABASE_URL=postgresql://orders:change-me-placeholder@db:5432/orders
rm k8s/db-deployment.yaml k8s/pgdata-persistentvolumeclaim.yaml
kubectl -n shop apply -f k8s/
kubectl -n shop apply -f db-statefulset.yaml
kubectl -n shop patch deploy orders --type=json -p "$(cat orders-patch.json)"
kubectl -n shop rollout status deploy/orders
kubectl -n shop get pods,svc,pvc
db-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata: {name: db}
spec:
serviceName: db
replicas: 1
selector: {matchLabels: {io.kompose.service: db}}
template:
metadata: {labels: {io.kompose.service: db}}
spec:
containers:
- name: db
image: postgres:17
env:
- {name: POSTGRES_USER, value: orders}
- {name: POSTGRES_DB, value: orders}
- name: POSTGRES_PASSWORD
valueFrom: {secretKeyRef: {name: db-secret, key: POSTGRES_PASSWORD}}
- {name: PGDATA, value: /var/lib/postgresql/data/pgdata}
ports: [{containerPort: 5432}]
readinessProbe: {exec: {command: [pg_isready, -U, orders]}, periodSeconds: 5}
resources: {requests: {cpu: 250m, memory: 256Mi}, limits: {memory: 512Mi}}
volumeMounts: [{name: data, mountPath: /var/lib/postgresql/data}]
volumeClaimTemplates:
- metadata: {name: data}
spec: {accessModes: [ReadWriteOnce], resources: {requests: {storage: 5Gi}}}
orders-patch.json
[
{"op":"replace","path":"/spec/template/spec/containers/0/env","value":[
{"name":"APP_ENV","value":"kubernetes"},
{"name":"REDIS_URL","value":"redis://cache:6379/0"},
{"name":"DATABASE_URL","valueFrom":{"secretKeyRef":{"name":"db-secret","key":"DATABASE_URL"}}}]},
{"op":"add","path":"/spec/template/spec/containers/0/readinessProbe","value":{"httpGet":{"path":"/ready","port":8080}}},
{"op":"add","path":"/spec/template/spec/containers/0/livenessProbe","value":{"httpGet":{"path":"/healthz","port":8080},"initialDelaySeconds":10}},
{"op":"add","path":"/spec/template/spec/containers/0/resources","value":{"requests":{"cpu":"100m","memory":"128Mi"},"limits":{"memory":"256Mi"}}},
{"op":"add","path":"/spec/template/spec/securityContext","value":{"runAsNonRoot":true,"runAsUser":10001}}
]
What each line does
db-secret— Passwords move out of plain env into a Secret with placeholder values.StatefulSet— Stable name db-0 and its own PVC from volumeClaimTemplates.PGDATA subfolder— Avoids the lost+found folder problem on fresh volumes.pg_isready— Readiness for Postgres, replacing depends_on: orders retries until db is ready.JSON patch— Adds probes, resources and non-root to the Kompose Deployment.
Green output
$ bash fix.sh
namespace/shop created
secret/db-secret created
deployment.apps/orders created
ingress.networking.k8s.io/orders created
statefulset.apps/db created
deployment.apps/orders patched
deployment "orders" successfully rolled out
NAME READY STATUS
pod/orders-7c9d8b6f4-2xkqp 1/1 Running
pod/cache-5d8f7c9b6-h4m2z 1/1 Running
pod/db-0 1/1 Running
NAME TYPE PORT(S)
service/orders ClusterIP 8080/TCP
service/db ClusterIP 5432/TCP
service/cache ClusterIP 6379/TCP
NAME STATUS CAPACITY
persistentvolumeclaim/data-db-0 Bound 5Gi
Expected result. Kompose plus a short checklist produced a hardened three-tier app on Kubernetes. In production, swap the db StatefulSet for Amazon RDS and keep only the DATABASE_URL Secret.
Every command, step by step (kind lab with kubectl)
-
Compose
printf ... > docker-compose.ymlWhat it does: File.
(no output) -
Up
docker compose up -dWhat it does: Local.
Started -
Version
kompose versionWhat it does: Installed.
1.38.0 (a8f5d1c) -
Convert
kompose convert -f docker-compose.yml -o k8s/What it does: Files.
INFO Kubernetes file "k8s/orders-deployment.yaml" created -
Gaps
grep -n POSTGRES_PASSWORD -A1 k8s/db-deployment.yamlWhat it does: Plain text.
value: change-me -
Secret
kubectl -n shop create secret generic db-secret ...What it does: Placeholder.
secret/db-secret created -
Apply
kubectl -n shop apply -f k8s/What it does: Objects.
deployment.apps/orders created -
DB
kubectl -n shop apply -f db-statefulset.yamlWhat it does: Stateful.
statefulset.apps/db created -
Patch
kubectl -n shop patch deploy orders --type=json -p ...What it does: Hardened.
deployment.apps/orders patched -
Check
kubectl -n shop get pods,svc,pvcWhat it does: Running.
pod/db-0 1/1 Running
When it is red 1
WARN Service "db" won't be created because 'ports' is not specified
Cause. No ports in compose for that service.
Fix, step by step
- Add the container port so a Service is created.
When it is red 2
ImagePullBackOff for image orders:dev
Cause. Local image name without registry.
Fix, step by step
- Use a pushed name like localhost:5001/orders:TAG.
When it is red 3
initdb: directory exists but is not empty (lost+found)
Cause. Volume root mounted as PGDATA.
Fix, step by step
- Set PGDATA to a subfolder.
When it is red 4
orders CrashLoopBackOff: could not connect to db
Cause. No depends_on; app starts before db.
Fix, step by step
- Retry in the app, or add an initContainer that waits for pg_isready.
When it is red 5
Unsupported key build
Cause. Compose build sections are ignored for Kubernetes.
Fix, step by step
- Build and push images first, then set image.
Practice tasks
Easy Task 1: Compose
Why: Write.
What to do: printf compose file.
- Three services.
Solution
docker compose configEasy Task 2: Up
Why: Run.
What to do: Start locally.
- curl healthz.
Solution
docker compose psEasy Task 3: Convert
Why: Kompose.
What to do: Generate k8s/.
- List files.
Solution
ls k8sMedium Task 4: Gaps
Why: Audit.
What to do: Find plain secrets and missing probes.
- grep.
Solution
grep -rn value: k8s/Medium Task 5: Secret
Why: Fix.
What to do: db-secret with placeholders.
- secretKeyRef.
Solution
kubectl -n shop get secret db-secretMedium Task 6: StatefulSet
Why: Fix.
What to do: Replace db Deployment.
- volumeClaimTemplates.
Solution
kubectl -n shop get sts,pvcHard Task 7: Harden
Why: Patch.
What to do: Probes, resources, non-root.
- JSON patch.
Solution
kubectl -n shop get deploy orders -o yamlHard Task 8: Meesho
Why: Main task.
What to do: Four services converted and fixed.
- Checklist.
Solution
kubectl -n meesho get allHomework: try at home
Take Meesho's compose file with web, worker, Postgres and RabbitMQ, convert it with Kompose, then fix it: Secrets for every password with placeholder values, StatefulSets for Postgres and RabbitMQ, probes, resources, non-root, registry image names and an Ingress for web.
Recap
- Kompose turns a compose file into Deployments, Services, PVCs and Ingresses in seconds.
- It copies secrets in plain text and skips probes, limits and hardening.
- depends_on disappears; apps must retry or wait with initContainers.
- Databases need StatefulSets or managed services, not Deployments.
- Always run the post-conversion checklist before applying.
FAQ
Q. Should I keep the Kompose output in Git?
Use it as a first draft, clean it up, then manage it with Kustomize or Helm as in earlier chapters.
Q. Can Kompose generate Helm charts?
Yes, kompose convert -c writes a basic chart, which still needs the same fixes.
Q. What about compose build sections?
Kompose can build and push with --build local, but CI from 66.4 is the better place for builds.
Q. Is Docker Swarm a similar path?
Swarm runs compose files directly; ch67 compares Swarm with Kubernetes.
Interview questions
-
How would you migrate a compose app to Kubernetes?
Model answer: Kompose for a draft, then secrets, probes, resources, security, StatefulSets or managed DBs, Ingress and CI.
-
What does Kompose not handle well?
Model answer: depends_on, secrets, health checks, stateful services, builds and security settings.
-
Deployment vs StatefulSet for Postgres?
Model answer: StatefulSet gives stable identity and a PVC per replica.
-
How do you replace depends_on?
Model answer: Application retries, readiness probes and initContainers.
Quick quiz
Q1. Does depends_on exist in Kubernetes?
No; apps retry or use initContainers.
Q2. What does a named volume become?
A PVC.
Q3. What should a database become?
A StatefulSet or a managed service.
Q4. Which label makes an Ingress?
kompose.service.expose.
Q5. Is Kompose output production ready?
No; it needs the checklist.
Samajla ka?
Lab
- Install Docker, kind, kubectl, and the Kompose binary from its official release page.
- Create a kind cluster named ch66 wired to a local registry on port 5001.
- Keep all files in a folder named orders-app.
- Never commit registry passwords; use Jenkins credentials and Kubernetes Secrets created from the command line.
- Delete the cluster at the end with kind delete cluster --name ch66.
Practice task
Take the orders API from source code to a running, scaled, health-checked Kubernetes Deployment: Dockerfile, image tagged with the Git commit, push to a registry, deploy with a Jenkins pipeline, then convert its docker-compose file with Kompose and compare the result.
Ravindra Bagale's Tip
Interview madhe 'Dockerfile pasun production paryant flow sanga', 'image tag latest ka vaparu naye' aani 'Kubernetes private image kasa pull karto' he prashna nakki yetat. Lab karun uttar dya.
In short
- Docker builds and ships the image; Kubernetes runs and heals it.
- Tag images immutably with the commit and deploy by tag or digest, never latest.
- Private registries need imagePullSecrets or node IAM roles.
- Jenkins automates build, test, push and deploy; Kompose is a starting point, not the final YAML.