Container Orchestration Without Kubernetes — Simpler Alternatives That Work
The Kubernetes Tax
A 15-person startup adopted Kubernetes because "that's what you use at scale." They hired a platform engineer to manage it. Then they needed another one. They spent more time debugging Kubernetes issues than building product features. Their monthly AWS bill tripled because of the control plane, node overhead, and the engineers needed to maintain it all.
Kubernetes solves real problems — for teams that have those problems. Most don't.
When You Don't Need Kubernetes
You probably don't need Kubernetes if:
✗ You have fewer than 10 services
✗ Your team is under 30 engineers
✗ You don't need multi-region deployment
✗ Your traffic is predictable (not bursty 100x)
✗ You don't have a dedicated platform team
✗ You're not running stateful workloads at scale
You might need Kubernetes if:
✓ You have 20+ services with complex networking
✓ You need multi-cloud or hybrid-cloud deployment
✓ You have a platform team (3+ engineers)
✓ You need fine-grained resource allocation
✓ You're running ML workloads with GPU scheduling
Alternative 1: Docker Compose + Managed Services
For teams with 1-5 services, this is often all you need:
# docker-compose.prod.yml
version: "3.8"
services:
api:
image: ${ECR_REPO}/api:${VERSION}
ports:
- "3000:3000"
environment:
- DATABASE_URL=${DATABASE_URL}
- REDIS_URL=${REDIS_URL}
deploy:
replicas: 3
resources:
limits:
memory: 512M
cpus: "0.5"
restart_policy:
condition: on-failure
max_attempts: 3
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
interval: 30s
timeout: 5s
retries: 3
worker:
image: ${ECR_REPO}/worker:${VERSION}
deploy:
replicas: 2
environment:
- QUEUE_URL=${SQS_QUEUE_URL}Pair with managed services: RDS for database, ElastiCache for Redis, SQS for queues. Let AWS handle the operational burden.
Alternative 2: AWS ECS (Fargate)
Serverless containers — no servers to manage, no clusters to configure:
// CDK definition for an ECS Fargate service
import * as ecs from "aws-cdk-lib/aws-ecs";
import * as ec2 from "aws-cdk-lib/aws-ec2";
const cluster = new ecs.Cluster(this, "ApiCluster", {
vpc,
containerInsights: true,
});
const taskDefinition = new ecs.FargateTaskDefinition(this, "ApiTask", {
memoryLimitMiB: 1024,
cpu: 512,
});
taskDefinition.addContainer("api", {
image: ecs.ContainerImage.fromEcrRepository(repo, "latest"),
portMappings: [{ containerPort: 3000 }],
logging: ecs.LogDrivers.awsLogs({ streamPrefix: "api" }),
healthCheck: {
command: ["CMD-SHELL", "curl -f http://localhost:3000/health || exit 1"],
interval: Duration.seconds(30),
retries: 3,
},
environment: {
NODE_ENV: "production",
},
secrets: {
DATABASE_URL: ecs.Secret.fromSsmParameter(dbUrlParam),
},
});
const service = new ecs.FargateService(this, "ApiService", {
cluster,
taskDefinition,
desiredCount: 3,
assignPublicIp: false,
circuitBreaker: { rollback: true }, // Auto-rollback failed deployments
});
// Auto-scaling based on CPU
const scaling = service.autoScaleTaskCount({ maxCapacity: 10, minCapacity: 2 });
scaling.scaleOnCpuUtilization("CpuScaling", { targetUtilizationPercent: 70 });Why ECS over Kubernetes: Same container benefits (isolation, reproducibility, scaling) without managing a control plane, etcd, networking plugins, or RBAC policies.
Alternative 3: Fly.io / Railway / Render
For teams that want zero infrastructure management:
# fly.toml — deploy containers to Fly.io
app = "my-api"
primary_region = "iad"
[build]
dockerfile = "Dockerfile"
[http_service]
internal_port = 3000
force_https = true
auto_stop_machines = true
auto_start_machines = true
min_machines_running = 2
[[services]]
protocol = "tcp"
internal_port = 3000
[services.concurrency]
type = "requests"
hard_limit = 250
soft_limit = 200
[[services.http_checks]]
interval = 10000
grace_period = "5s"
method = "get"
path = "/health"
timeout = 2000# Deploy with one command
fly deployComparison Matrix
| Feature | Docker Compose | ECS Fargate | Fly.io | Kubernetes |
|----------------------|---------------|-------------|-----------|------------|
| Setup time | Hours | Days | Minutes | Weeks |
| Operational burden | Low | Low-Medium | Very low | High |
| Auto-scaling | Manual | Built-in | Built-in | Built-in |
| Cost (small team) | $50-200/mo | $100-500/mo | $50-300/mo| $300+/mo |
| Multi-region | Hard | Possible | Easy | Complex |
| Service mesh | No | App Mesh | Built-in | Istio |
| Learning curve | Low | Medium | Low | High |
| Team size needed | 0 (part-time) | 0.5 eng | 0 | 1-3 eng |
| Max services | ~5 | ~20 | ~15 | Unlimited |
The Migration Path
Start simple and upgrade only when you hit limits:
Stage 1 (0-5 services, < 10 engineers):
→ Docker Compose on a single server or Fly.io/Railway
→ Managed database and cache
→ GitHub Actions for CI/CD
Stage 2 (5-15 services, 10-30 engineers):
→ ECS Fargate or Google Cloud Run
→ Service discovery built in
→ Auto-scaling and health checks
→ Still no Kubernetes
Stage 3 (15+ services, 30+ engineers, dedicated platform team):
→ Consider Kubernetes (EKS/GKE managed, not self-hosted)
→ Only if you have the team to support it
→ Platform team abstracts K8s complexity from product engineers
The best infrastructure is the one your team can operate confidently. A perfectly configured ECS service that your team understands beats a Kubernetes cluster that only one person can debug. Choose the simplest tool that meets your actual requirements — not the requirements you might have in two years.