The Container Orchestration Model
A single container running on a single machine has no opinion about what happens when that machine dies, when traffic doubles, or when a new version needs to replace the old one without dropping requests.
Search across all documentation pages
A single container running on a single machine has no opinion about what happens when that machine dies, when traffic doubles, or when a new version needs to replace the old one without dropping requests.
An orchestrator - Kubernetes, ECS, Cloud Run - exists specifically to hold those opinions on your behalf, continuously, without a human re-running a deploy script every time reality drifts from what you asked for.
Platform Deploy Basics shows the 12-factor practices and manifests that make a Node service orchestrator-friendly; this page is the model underneath - what an orchestrator actually does between the moment you submit a spec and the moment your service is running, healthy, and staying that way.
docker run.A docker run command starts one container, once, on one machine, and does nothing further - if that machine reboots or the process crashes, nothing brings it back unless a person or a separate script intervenes.
That's fine for a single experiment; it falls apart the moment a service needs multiple replicas across multiple machines, needs to survive a machine failing outright, or needs a new version deployed without a gap where no replica is serving traffic.
An orchestrator's central idea is desired state: instead of issuing imperative commands ("start this container now"), you declare what should be true - "3 replicas of image X should be running, exposing port 3000" - and hand that declaration to a control plane, a set of orchestrator components that continuously work to make it real.
The mechanism that does the actual work is a reconciliation loop: the control plane repeatedly compares desired state against observed actual state, and whenever they diverge - a replica crashed, a node went offline, someone edited the desired count - it takes whatever action closes the gap.
A useful analogy: a thermostat doesn't "turn on the heat once" - it continuously compares the room's actual temperature to the target and acts whenever they differ, for as long as it's running.
An orchestrator does the same thing for container replicas instead of temperature, running that comparison indefinitely rather than as a one-time deploy step.
Three orchestrator concerns map onto three different questions, and keeping them separate clarifies what's actually happening during a deploy or a failure.
The scheduler answers where: given a set of machines (nodes) with available CPU and memory, it decides which node should run each new replica, taking resource requests and constraints into account.
Health checks answer whether: a readiness check tells the control plane whether a replica should currently receive traffic at all, while a liveness check tells it whether the replica needs to be killed and replaced because it's stuck or unresponsive - these are different questions with different consequences, which is why platforms expose them as separate probes rather than one.
Replica count answers how many: the desired-state declaration includes a target replica count, and the reconciliation loop's job includes noticing when actual running replicas fall short (a crash) or need to change (a scaling decision) and correcting it.
Desired state: 3 replicas of api:sha-abc123
|
control plane's reconciliation loop
|
observe actual state -> compare -> act if different
- replica crashed? -> schedule a replacement
- node went offline? -> reschedule its replicas elsewhere
- desired count changed? -> create or remove replicas
|
loop repeats, continuously, forever
A rolling update is this same reconciliation model applied to a version change: the orchestrator doesn't stop all old replicas and start new ones at once, it incrementally starts new replicas, waits for each to pass its readiness check, then removes an old replica - repeating until the new version fully replaces the old one, with capacity maintained throughout.
That sequencing is exactly why readiness checks matter for zero-downtime deploys: a new replica that hasn't yet passed its readiness check should not receive traffic, or the rollout would route real requests to a replica that isn't actually ready to handle them, producing errors during every deploy instead of none.
The orchestrator also has no way to know that your application handled a shutdown signal cleanly unless your code cooperates - it sends a termination signal and waits a grace period, but a Node process that doesn't drain in-flight requests before exiting will still drop them, regardless of how well the orchestration layer behaved.
Different orchestrators sit at different points on a control-versus-simplicity spectrum, and picking one is really picking how much of that reconciliation machinery you want to operate yourself.
Kubernetes exposes the full model directly - Deployments, Services, custom resources, its own scheduler you can tune - which gives maximum control at the cost of running (or paying someone to run) the control plane and learning its concepts in depth.
ECS Fargate keeps the same desired-state/reconciliation idea but scopes it to AWS, trading some of Kubernetes's flexibility (no custom resources, no multi-cloud portability) for meaningfully less operational surface to own.
Cloud Run goes further toward simplicity by hiding almost all of the model behind an HTTP-request abstraction - you still get automatic scaling and recovery, but you give up direct control over scheduling, node placement, and non-HTTP event sources in exchange for near-zero orchestration configuration.
Autoscaling extends the same reconciliation idea one layer further: a Horizontal Pod Autoscaler (or equivalent) doesn't just keep the declared replica count running, it changes the declared count itself based on observed metrics like CPU or request rate, making desired state a moving target the loop continuously chases rather than a fixed number.
Failure domains matter more as orchestration scales: a PodDisruptionBudget-style guarantee exists because a scheduler rescheduling replicas during a node drain could otherwise take an entire service offline if it happens to remove too many replicas at once - the reconciliation loop needs guardrails, not just goals.
| Orchestrator | Strength | Weakness | Best Fit |
|---|---|---|---|
| Kubernetes | Full control; portable across clouds; rich ecosystem | Real operational overhead; steep learning curve | Multi-service platforms, teams with dedicated platform capacity |
| ECS Fargate | AWS-native; less YAML and cluster management than K8s | Locked to AWS; fewer extensibility points | AWS-only shops wanting less operational burden than K8s |
| Cloud Run | Near-zero config; scales HTTP workloads automatically | Least control; HTTP-request-shaped workloads only | Simple, stateless HTTP APIs with unpredictable traffic |
It's a declaration of what should be true - for example, "3 replicas of this image should be running" - rather than an imperative command to run once. The orchestrator's control plane is responsible for continuously making that declaration real.
Through the reconciliation loop: the control plane continuously compares desired state (the declared replica count) against actual observed state, and when a crash makes them diverge, it schedules a replacement to close the gap.
The scheduler answers one specific question - which node should run a given replica - based on available resources. The reconciliation loop is the broader continuous process that notices state has diverged and decides, among other things, when the scheduler needs to act.
They answer different questions with different consequences: readiness decides whether a replica should currently receive traffic, while liveness decides whether a replica is stuck and needs to be killed and replaced. Treating them as one check risks either routing traffic to an unready replica or killing a replica that was just temporarily busy.
It replaces old replicas with new ones incrementally, only removing an old replica after its replacement has passed its readiness check - so there's always a sufficient number of ready replicas serving traffic throughout the deploy, never a gap where none are.
Yes - the orchestrator sends a termination signal and waits a grace period, but it's the application's job to stop accepting new work and finish in-flight requests during that window. An app that ignores the signal will still drop requests regardless of how well the orchestrator behaved.
They sit on a spectrum of control versus operational simplicity: Kubernetes gives the most control at the highest operational cost, Cloud Run gives the least control but requires almost no orchestration configuration, and ECS Fargate sits between the two, AWS-native but simpler than Kubernetes.
Autoscaling changes the desired replica count itself, based on observed metrics like CPU or request rate, and the same reconciliation loop that handles crashes then works to realize that new, moving target - it's a policy on top of the base model, not a separate system.
Because the reconciliation loop is continuously comparing actual state to the declared desired state in configuration - a manual change that isn't reflected in that declared state is, from the control plane's perspective, drift to be corrected, not a permanent update.
It stops the reconciliation loop's own maintenance actions - like rescheduling replicas off a node being drained - from accidentally taking too many replicas of the same service offline at once, which the scheduler alone wouldn't otherwise prevent.
No - ECS Fargate and Cloud Run implement the same core desired-state and reconciliation model with less operational surface to own, trading away some of Kubernetes's flexibility and portability in exchange.
No - it also covers scheduling replicas across machines, zero-downtime version rollouts, and autoscaling, all driven by the same underlying reconciliation loop comparing desired state to actual state.
Stack versions: This page is conceptual and not tied to a specific stack version.
Reviewed by Chris St. John·Last updated Jul 15, 2026