Docker Compose is great — until you need a second server. This lesson motivates container orchestration: what breaks at scale, what problems orchestrators solve, and the declarative mental model that underpins every orchestration tool from Kubernetes to Nomad.
1. The Single-Host Ceiling
Docker Compose runs all your services on one machine. That works beautifully for development and small production workloads. But production at scale demands things a single host cannot provide:
- Redundancy — if the host dies, everything dies with it
- Capacity — one server has finite CPU, RAM, and disk
- Rolling updates — you can't update a service with zero downtime if there's only one instance
- Auto-recovery — restart policies help, but who restarts the host?
- Geographic distribution — latency demands services near users
The moment you need a second server, you face a wall of new problems that docker-compose up was never designed to solve.
2. The Problems Orchestration Solves
Orchestration solves eight fundamental problems:
Scheduling
Which host runs which container? An orchestrator performs bin-packing — fitting containers onto nodes based on available CPU, memory, and other constraints. You don't SSH into a server and run docker run; the scheduler decides the optimal placement.
Self-Healing
A container crashes? The orchestrator detects it (via health checks) and automatically restarts or reschedules it on a healthy node. A whole node goes down? Its workloads migrate to surviving nodes — no human intervention needed.
Scaling
Need more capacity? Tell the orchestrator "run 10 replicas" instead of 3. It finds nodes with room and spins them up. With autoscaling, this happens automatically based on metrics — CPU usage, request count, queue depth.
Rolling Updates
Deploy v2 without downtime: the orchestrator replaces instances one-by-one, health-checking each new replica before killing an old one. If the new version is unhealthy, it automatically rolls back.
Service Discovery
How does service A find service B when B might be on any of 50 nodes? Orchestrators provide built-in DNS and service registries. Containers refer to each other by name; the platform resolves the rest.
Load Balancing
Traffic is distributed across all healthy replicas of a service. The orchestrator maintains the mapping and removes unhealthy instances from rotation automatically.
Secret Management
Secrets (API keys, TLS certs, database passwords) need to reach containers on any node without being baked into images. Orchestrators provide encrypted secret distribution — inject at runtime, rotate without redeployment.
Resource Management
Prevent a runaway service from consuming all CPU/memory on a node. Orchestrators enforce resource requests and limits — guaranteeing minimums and capping maximums per container.
3. The Evolution
How did we get here? Each step was a response to the previous one's limitations:
| Era | Approach | What Broke |
|---|---|---|
| Manual | SSH in, docker run, configure by hand |
Human error, no reproducibility, doesn't scale past 3–5 servers |
| Shell scripts | Bash scripts for deploy, rollback, health checks | Fragile, no state awareness, each script is bespoke |
| Config management | Ansible/Chef/Puppet push desired config to hosts | Declarative for host state but not container-aware. No scheduling, no failover, no scaling. |
| Container orchestration | Kubernetes, Swarm, Nomad — cluster-level scheduling | Complexity! But it solves the multi-host container problem properly. |
Config management tools like Ansible are excellent for provisioning hosts, but they operate on a push model — they don't continuously watch and reconcile. Orchestrators run a continuous control loop: observe → diff → act → repeat.
4. Orchestrator Landscape
| Tool | Positioning | Best For |
|---|---|---|
| Kubernetes | Industry standard, massive ecosystem, steep learning curve | Teams needing full control, multi-cloud, large scale |
| Docker Swarm | Built into Docker, simple setup, limited features | Small teams wanting orchestration without K8s complexity |
| HashiCorp Nomad | Flexible (containers, VMs, binaries), simple architecture | Mixed workloads, teams already using HashiCorp tools |
| Amazon ECS | AWS-native, tight integration, no cluster management | AWS-committed teams wanting managed orchestration |
| Cloud Run / Fargate | Serverless containers — no nodes to manage at all | Stateless HTTP services, event-driven workloads |
5. Do You Need an Orchestrator?
Not always. Orchestration adds real complexity — more moving parts, more concepts to learn, more things that can go wrong. For many workloads, simpler solutions are perfectly valid:
- Single server + Compose + systemd — handles restarts, runs your stack, good for small-to-medium traffic
- Managed PaaS (Render, Railway, Fly.io) — orchestration hidden behind a UI/CLI
- Serverless containers (Cloud Run, Fargate) — auto-scaling without cluster management
Decision Framework
| Factor | You Probably Don't Need Orchestration | You Probably Do |
|---|---|---|
| Team size | 1–3 engineers | 5+ engineers, multiple services |
| Scale | Fits on one beefy server | Multiple nodes required |
| Availability | Minutes of downtime acceptable | 99.9%+ SLA required |
| Deployment freq | Weekly or less | Multiple deploys per day |
| Services | Monolith or 2–3 services | 10+ microservices |
6. The Declarative Model
The most important concept across all orchestrators is the declarative reconciliation loop:
- You declare desired state: "I want 3 replicas of nginx:1.25, each with 256MB RAM"
- The orchestrator observes current state: "There are 2 replicas running, one is unhealthy"
- It computes the diff: "Need to kill the unhealthy one and start 2 new ones"
- It acts to reconcile — then loops back to step 2
This is fundamentally different from imperative commands ("start a container", "stop that one"). You never say how — you say what you want, and the system figures out the how, continuously.
This model means the orchestrator is self-correcting. Drift from desired state — whether caused by crashes, network failures, or resource pressure — is automatically detected and fixed.
# Declarative: you state WHAT you want
replicas: 3
image: nginx:1.25
resources:
limits:
memory: 256Mi
# The orchestrator figures out HOW:
# - Which nodes have capacity?
# - Which replicas are already running?
# - Which need to be created/destroyed?
🧪 Interactive Quizzes
Quiz 1: When is orchestration needed?
Your startup has a 3-service app running on a single $80/month server. Traffic is moderate and 5 minutes of downtime during deploys is acceptable. You deploy once a week. Should you adopt Kubernetes?
Correct! This workload fits comfortably on a single host. The team is small, downtime tolerance is reasonable, and deploy frequency is low. Adding Kubernetes would introduce massive operational complexity for zero benefit. Compose + systemd + a solid backup strategy is the right answer here.
Quiz 2: Declarative vs. Imperative
Which of these is a declarative statement?
Correct! "Ensure 3 healthy replicas are always running" describes the desired state without specifying how to achieve it. The orchestrator decides where to place them, when to restart them, and how to handle failures. The other options are all imperative — they specify exact steps to execute.
Quiz 3: What does the scheduler do?
In orchestration, what is the primary job of the scheduler?
Correct! The scheduler's job is placement — examining available resources across all nodes (CPU, memory, disk, affinity rules) and choosing the optimal node for each container. This is bin-packing: fitting workloads onto nodes efficiently. It has nothing to do with time-based scheduling (that's a separate concept).
🔬 Hands-On Task
Task: Identify What Breaks at Multi-Host Scale
Take this Compose file and identify everything that would break if you needed to run it across 3 servers:
version: "3.8"
services:
web:
image: myapp:latest
ports:
- "80:8080"
environment:
- DB_HOST=db
- REDIS_HOST=cache
depends_on:
- db
- cache
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=secret123
cache:
image: redis:7-alpine
worker:
image: myapp:latest
command: ["./worker"]
environment:
- DB_HOST=db
- REDIS_HOST=cache
volumes:
pgdata:
Questions to Answer:
- Service discovery:
DB_HOST=dbrelies on Compose's internal DNS. What happens ifdbruns on server-2 butwebruns on server-1? - Port conflicts: If you run 3 replicas of
webon the same host, what happens with port 80? - Volume locality: The
pgdatavolume is local to one host. What if Postgres gets rescheduled to a different node? - Secrets:
POSTGRES_PASSWORD=secret123is in plaintext. How would you distribute this across 3 servers securely? - Load balancing: With 3 replicas of
web, how does traffic reach all of them? - Failover: If server-1 dies and it was running
db, what happens?
Reveal Analysis
- Discovery breaks. Compose DNS is host-local. Across hosts, you need a cluster-wide DNS or service registry (which orchestrators provide).
- Port conflict. Only one process can bind port 80. You need a load balancer in front, and containers should use dynamic ports.
- Data loss risk. Local volumes don't follow containers. You need network-attached storage (EBS, NFS, Ceph) or a database service.
- Secrets exposure. You'd need to copy env files to each server or use a secret management system (Vault, orchestrator secrets).
- No load balancing. You need an external LB (nginx, HAProxy, cloud LB) or the orchestrator's built-in service mesh.
- Total outage for that service. No failover mechanism exists — an orchestrator would reschedule to a surviving node.
Key Takeaways
- Single-host has a ceiling — Compose can't provide multi-node redundancy, failover, or horizontal scaling
- Orchestrators solve 8 core problems — scheduling, self-healing, scaling, rolling updates, service discovery, load balancing, secret management, and resource management
- The industry evolved from manual → scripts → config management → orchestration, each step addressing the previous one's inadequacy
- Not every app needs orchestration — complexity has a cost. Use the decision framework to evaluate honestly
- Declarative > imperative — state what you want, let the system figure out how. The reconciliation loop is the fundamental mental model
- Kubernetes dominates but isn't the only option — Swarm, Nomad, ECS, and serverless containers each serve different niches