When Compose stops being enough
Docker Compose is a wonderful tool, and many teams quietly run production on it for longer than they should admit. The signs you have outgrown it are practical rather than philosophical. A single host is a single point of failure. Deploys cause a short outage because there is no rolling update. A busy week means resizing a server by hand. Two services need different scaling. You want zero-downtime releases, per-service autoscaling or a clean path to multiple environments.
At that point an orchestrator earns its complexity. This work forms part of our Docker containerisation service and connects to our Kubernetes and EKS consulting.
Choosing the target honestly
| Option | Best when | Cost of choosing it |
|---|---|---|
| Stay on Compose (single host, good backups) | Small system, tolerant of brief downtime | Manual scaling, limited resilience |
| Amazon ECS on Fargate | AWS-only, small team, want low operations | AWS-specific concepts, less portable |
| Kubernetes (EKS, AKS, GKE) | Many services, portability, ecosystem and GitOps | Higher learning curve and platform ownership |
We tell clients when the cheapest correct answer is not to migrate. A team of three running a modest product does not need a cluster. If you are unsure, our DevOps maturity assessment is a useful starting conversation.
How a Compose service maps
The concepts translate well once you know where each piece goes:
| Compose | Kubernetes | ECS |
|---|---|---|
services: entry | Deployment plus Service | Task definition plus service |
environment: | ConfigMap and Secret | Task environment and Secrets Manager references |
ports: | Service and Ingress | Load balancer target group |
volumes: | PersistentVolumeClaim | EFS or EBS (or managed service) |
healthcheck: | Liveness, readiness, startup probes | Container health check |
depends_on: | Not ordered, apps must tolerate retries | Dependencies are limited, apps must retry |
deploy.replicas | replicas and HPA | Desired count and autoscaling |
The last two rows matter. Orchestrators do not guarantee startup order, so services must handle a missing dependency gracefully. This is one reason we prepare the application before touching manifests.
A translated service
A Compose service becomes a Deployment, and the details Compose never asked for become explicit:
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 3
selector:
matchLabels: { app: api }
strategy:
rollingUpdate: { maxUnavailable: 0, maxSurge: 1 }
template:
metadata:
labels: { app: api }
spec:
containers:
- name: api
image: registry.example.com/platform/api:1.4.2
ports: [{ containerPort: 8080 }]
envFrom:
- configMapRef: { name: api-config }
- secretRef: { name: api-secrets }
resources:
requests: { cpu: 250m, memory: 256Mi }
limits: { memory: 512Mi }
readinessProbe:
httpGet: { path: /healthz, port: 8080 }
livenessProbe:
httpGet: { path: /livez, port: 8080 }
None of the numbers above are recommendations: requests and limits come from measuring your workload. Packaging the manifests with Helm or Kustomize, and delivering them through GitOps, keeps environments consistent.
Making services ready before they move
Most of the effort in a migration is not in the manifests. It is in the application. Before we translate anything, we check that each service meets a short list of conditions:
- It reads configuration from environment variables or mounted files, not from files baked into the image or edited on the host.
- It logs to standard output, so the platform can collect logs without a sidecar or a shared volume.
- It shuts down cleanly on SIGTERM, finishing in-flight requests, because orchestrators stop containers routinely during deploys and node changes.
- It exposes a cheap health endpoint that distinguishes “alive” from “ready to serve”.
- It tolerates its dependencies being briefly absent, with retries and backoff.
Fixing these in the Compose world first is useful in its own right, because the same changes make the existing deployment more reliable. It also lets the migration proceed as a series of small, reversible steps.
Networking and traffic
Compose gives every service a DNS name on a shared network and publishes ports on the host. Orchestrators separate these concerns. On Kubernetes, a Service gives stable internal naming and an ingress controller or load balancer handles external traffic; on ECS, a load balancer target group usually fronts each public service. Decide early how TLS certificates are issued and renewed, how internal-only services stay private, and whether you need network policies to restrict traffic between services. Compose’s flat network hides these questions, and teams meet them for the first time in staging.
Cost and team impact
An orchestrator adds running costs (control plane, extra nodes for headroom, load balancers) and, more importantly, people costs: someone must own upgrades, access control and incident response. If you do not have that capacity, managed Fargate or a managed DevOps arrangement reduces the load. We would rather quote the ongoing cost honestly now than have it surprise you in six months.
Dealing with state
Databases and queues deserve their own plan. Our default advice is to use managed services (RDS, Azure Database, Cloud SQL, managed Redis) rather than running them in the new platform on day one. That separates two risky changes: moving compute and moving data. If a database must run in the cluster, it needs persistent volumes, tested backups and a restore drill, and we tie that into backup and disaster recovery.
Cutover without drama
- Build the target platform and deploy the workload with no traffic.
- Run smoke, load and failure tests, including killing pods or tasks.
- Send a small share of traffic through weighted DNS or a load balancer.
- Watch error rates and latency, then increase gradually.
- Keep the Compose host running and routable until the new platform has handled a full business cycle.
- Decommission only after the rollback window passes.
Common failure modes
- Treating the conversion as the migration. Auto-generated manifests lack probes, limits and security settings.
- Hidden host dependencies such as files on the Docker host, cron jobs or host networking.
- Config in the image that must now be externalised.
- Underestimating the platform work: logging, monitoring, ingress, certificates and CI deploys.
- Skipping the rollback test.
Delivery and handover
We deliver the manifests or task definitions as code, the CI/CD deploy path, runbooks and a handover session. Your team should be able to deploy, roll back and debug on the new platform without us.
How to tell it is working
Deploys happen without downtime, a failed instance is replaced automatically, scaling is a setting rather than a project, and your team can explain how a release reaches production. To discuss whether and where to migrate, contact us.