Docker Compose vs Kubernetes: the concrete signals that you have outgrown Compose, and the cheaper middle steps (K3s, ECS, Cloud Run) to try first.
A four-person team inherited a Compose-based inference stack, and every planning document carried the same note: "move to Kubernetes eventually." Eighteen months later they had moved exactly one component, the model-serving layer, and left everything else on Compose. Deploys stayed simple, on-call stayed quiet, and the infrastructure bill stayed small.
That is a composite scenario, but the pattern is common. Moving to Kubernetes is a decision with a real cost, and the skill is knowing which named limit justifies paying it.
Yes, for many single-product teams. Docker Compose describes services, networks and volumes in one file and runs them on one host, which is all many workloads need. Its limits are the single host (one machine is a single point of failure) and no built-in rolling deployments or autoscaling.
A production-grade Compose file needs more than the tutorial version. Pin image tags, add health checks, set restart policies and limit resources:
services: api: image: registry.internal.example.net/myapp:1.8.2 restart: unless-stopped ports: - "8080:8080" env_file: .env.production depends_on: db: condition: service_healthy healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8080/health"] interval: 10s retries: 3 deploy: resources: limits: cpus: "1.0" memory: 512M db: image: postgres:16 restart: unless-stopped volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U app"] interval: 10s volumes: postgres_data:Avoid :latest in production, because it makes rollbacks guesswork. Keep secrets in an environment file or a secrets manager, not in the YAML.
You have outgrown Compose when you hit a limit you can name and measure, not when the tooling feels unfashionable. These are the signals that justify a move:
Service count is a weak signal by itself. The old rule of thumb of 10 to 15 microservices is a prompt to look closer, not a trigger.
Run two copies behind a reverse proxy and switch traffic only when the new copy is healthy. Compose itself recreates containers in place, which causes a short gap, so the proxy provides the continuity.
A simple blue-green sequence uses two services (api-blue and api-green) that share one image repository but run different tags:
## start the new color and wait for health checks to passdocker compose up -d --wait api-green## the proxy health-checks both upstreams, so traffic can shift## once green is healthy, then stop the old colordocker compose stop api-blueThe --wait flag blocks until services report healthy. If you want rolling updates as a built-in feature without Kubernetes, Docker Swarm's update_config provides them, but weigh its smaller community before choosing it for a long-lived platform.
Three options cover most teams that outgrow one host but do not need full Kubernetes: a managed container service, serverless containers, or a lightweight Kubernetes distribution. Treat each as a legitimate destination, not just a stepping stone.
## pin the version instead of installing whatever is newestcurl -sfL https://get.k3s.io | \ INSTALL_K3S_VERSION=v1.31.4+k3s1 sh -kubectl get nodesCheck the K3s release list for a current version before running this. Piping a script to a shell is convenient, so review it first on production machines.
The control plane is the smallest part of the bill. A managed EKS control plane lists at about $0.10 per hour, roughly $73 a month, but the larger costs are nodes, load balancers, observability, upgrade work and people who can debug it at 03:00.
Kubernetes ships about three minor releases a year, and each supported version has a limited support window, so you inherit a steady upgrade cadence. You also take on RBAC, network policies, ingress, storage classes and admission rules. Budget for a platform owner, even if it is a fraction of one engineer's time. If nobody can own that, a managed container service is the safer choice.
Use kompose for a first pass, then harden the output by hand. It translates services into Deployments and Services and volumes into PersistentVolumeClaims, which is a real head start but not production-ready.
kompose convert -f docker-compose.yml -o k8s/kubectl apply --dry-run=client -f k8s/Then work through this checklist:
depends_on ordering with probes and retry logic in the application.Migrate one service first, ideally a stateless one, and keep the rest on Compose until the first move is stable.
| Option | Best for | Main cost |
|---|---|---|
| Compose on one VM | Small teams, one product | Single host failure |
| ECS or Cloud Run | Cloud-native, few services | Provider lock-in |
| K3s | Edge, small clusters | You run upgrades |
| Managed Kubernetes | Many teams, many services | Platform effort |
There is no ladder you must climb. A team can stay on ECS indefinitely, and another can jump straight to managed Kubernetes if it needs multi-team isolation from day one.
docker-compose.yml for local development after migrating, and share image names and health endpoints with your manifests.References and Further Reading
- Docker Compose Documentation - File reference, health checks and
--wait- Kompose - Convert Compose files to Kubernetes manifests
- K3s Documentation - Requirements and installation options
- Kubernetes HPA - Official autoscaling reference
A named, measured limit: the largest practical single host is sustained above roughly 70% CPU or memory, you need high availability across hosts, you need zero-downtime rollouts that a reverse proxy cannot give you, or several teams need to deploy independently. "Kubernetes sounds more serious" is not a signal.
Put a reverse proxy such as Caddy, Traefik or nginx in front, run two copies of the service with health checks, and shift traffic only after the new copy reports healthy. Docker Swarm's rolling update is the other built-in route, though its ecosystem is much smaller than Kubernetes.
K3s is a CNCF-certified Kubernetes distribution with a single-binary install and low overhead, and many teams run it permanently. The trade-off is that you operate the cluster, including upgrades, backups of the datastore and node security, which a managed service would otherwise do.
It produces a usable first pass of Deployments, Services and PersistentVolumeClaims, but not production settings. Add readiness and liveness probes, resource requests and limits, an Ingress, real Secrets, and storage classes yourself, and review every `depends_on`, which has no Kubernetes equivalent.
Yes for local development, where `docker compose up` gives a fast full stack on a laptop. The risk is drift between the Compose file and the cluster manifests, so share image names, environment variable names and health check endpoints across both.
Discussion0