docker-compose to Kubernetes

Convert a docker-compose.yml into Kubernetes Deployment and Service manifests.

docker-compose.yml Input
Kubernetes Manifests Output
Kubernetes manifests appear here

Related Tools

Documentation

What is docker-compose to Kubernetes?

This tool converts a docker-compose.yml file into the closest equivalent set of Kubernetes manifests — a Deployment and, if the service exposes ports, a matching Service per compose service, plus a PersistentVolumeClaim for each named volume. It's a starting point for moving a local Compose stack onto a cluster, not a drop-in replacement for hand-tuned production YAML.

How it works

Each service's image, ports, environment, and volumes become a single-container Deployment with replicas taken from deploy.replicas (defaulting to 1). Docker Compose service and volume names are lowercased and stripped of characters Kubernetes doesn't allow in object names (only lowercase letters, digits, and - are valid). Manifests are serialized with js-yaml and joined with --- document separators so the whole output can be piped straight into kubectl apply -f.

Named volumes (e.g. db-data:/var/lib/postgresql/data) get a dedicated PersistentVolumeClaim (1Gi default, one per unique volume name, deduplicated across services), since that's the portable, multi-node-safe equivalent. Bind mounts (a literal host path like ./html:/usr/share/nginx/html) become hostPath volumes instead, with a comment warning that hostPath only works if that exact path exists on whichever node the pod is scheduled to.

Features

  • Generates Deployment + Service pairs, ready for kubectl apply
  • Named volumes become deduplicated PersistentVolumeClaim objects; bind mounts become hostPath with a portability warning
  • Sanitizes compose service/volume names into valid Kubernetes RFC 1123 object names
  • Flags depends_on and build-only services with explanatory comments rather than guessing
  • Carries deploy.replicas straight into spec.replicas
  • Copy, download as a single .yaml file, or load sample data

Example

Input:

services:
  db:
    image: postgres:15
    environment:
      POSTGRES_PASSWORD: secret
    ports:
      - "5432:5432"
    volumes:
      - db-data:/var/lib/postgresql/data

Output:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: db-data
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
# ^ default 1Gi size — adjust to what db-data actually needs.
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: db
spec:
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - name: db
          image: postgres:15
          ports:
            - containerPort: 5432
              protocol: TCP
          env:
            - name: POSTGRES_PASSWORD
              value: secret
          volumeMounts:
            - name: db-data
              mountPath: /var/lib/postgresql/data
      volumes:
        - name: db-data
          persistentVolumeClaim:
            claimName: db-data
---
apiVersion: v1
kind: Service
metadata:
  name: db
spec:
  selector:
    app: db
  ports:
    - port: 5432
      targetPort: 5432
      protocol: TCP

Common errors

"No services found" means the input is missing a top-level services: map. A service with no Deployment in the output, only a comment, means it had a build: section but no image: — Kubernetes can't build images, so you need to build and push one to a registry first. A service or volume name with underscores or uppercase letters gets silently rewritten to a hyphenated lowercase equivalent to satisfy Kubernetes naming rules — check the generated metadata.name matches what you expect if you're scripting around it.

Best practices

Treat the output as a first draft: resize the generated PVCs to real storage requirements, replace any hostPath volume with a proper PVC before running on more than one node, and add liveness/readiness probes plus resource requests/limits, none of which a docker-compose file has an equivalent for. If services had depends_on, add a readiness probe or initContainer to actually enforce that ordering — Kubernetes won't infer it from the comment.

Frequently Asked Questions

What happens to a service that only has a "build" section, no "image"?

Kubernetes can't build a container image itself — it only runs images that already exist in a registry. A build-only service is skipped with a comment explaining you need to build and push that image first, rather than generating a Deployment that references an image that doesn't exist anywhere.

How are bind-mount volumes (./data:/path) handled?

Converted to hostPath volumes, with a comment flagging the real limitation: hostPath only works if that exact path exists on the specific node the pod happens to land on, which breaks the moment you have more than one node. For anything beyond local single-node testing (e.g. minikube), use a PersistentVolumeClaim instead — named volumes already get one generated automatically.

Does depends_on control startup order in the output?

No — Kubernetes doesn't have a direct equivalent to depends_on, so it's surfaced as a comment rather than faked with something that wouldn't actually enforce the ordering. If strict startup order matters, add a readiness probe to the dependency and/or an initContainer that waits for it.

Are named volumes and bind mounts treated differently?

Yes — a named volume (e.g. db-data:/var/lib/postgresql/data) gets its own PersistentVolumeClaim generated (default 1Gi, meant to be resized to what you actually need), since that's the portable, cluster-friendly equivalent. A bind mount (a real host path) becomes hostPath instead, since a PVC would silently change the semantics of "this specific directory on my machine."