Docker & ContainersX · Production ArchitectureBoundaries
Operational boundaries — when Docker stops being the right tool
What you'll learn
- Identify the requirement that genuinely forces an orchestrator
- Recognise the home-grown orchestrator you are accidentally building
- Define ownership, escalation and change authority for a Docker stack
- Plan a migration that does not start with a rewrite
Prerequisites
Verified against Docker Engine 29.x · Docker Engine 28.x · Docker Compose 2.x · containerd 2.x · runc 1.2.x · BuildKit 0.20+ · Linux kernel 5.15+ · Ubuntu 24.04 LTS · Debian 12 (Bookworm) · 2026-08-12
Docker is a container runtime. Compose is a single-host tool that drives it from a file. Between them they will run a real production service for years, and there is a point past which they will not.
There are actually two boundaries, and teams routinely diagnose one as the other. A stack that pages the wrong person at 03:00 does not have a scheduling problem, and no orchestrator will fix it.
The technical boundary
Compose cannot do four things, and no amount of scripting will add them.
- Multi-host scheduling. Nothing decides which host a container runs on, because nothing knows there is more than one host.
- Self-healing across host failure. A restart policy needs a live daemon on a live host. If the host is gone, the policy is gone with it.
- Rolling deploys.
upstops a container and then starts a new one. There is no second replica to shift traffic to and nothing to shift it with. - Autoscaling. There is no controller reading a metric and adjusting a replica count.
| Requirement | Can Compose do it? | Honest alternative on one host |
|---|---|---|
| Restart a crashed container | Yes | restart: unless-stopped |
| Survive a daemon restart | Yes | live-restore: true |
| Survive a host reboot | Yes | Restart policies plus a boot check |
| Survive a host failure | No | A second host, and something to fail over |
| Deploy without dropping requests | No | A proxy that retries, and a short replacement window |
| Run more replicas than one host holds | No | — |
| Move a workload off a failing disk | No | — |
The three “no” rows are the boundary. Everything above them is a configuration problem you already know how to solve.
Choosing, if you must
Docker Swarm. Built into the engine; docker swarm init and you have a
cluster. Compose files translate almost directly, because deploy: was
designed for it. Genuinely simple, genuinely limited, and in maintenance
rather than active development — which is fine for a stable internal
service and a poor bet for something you expect to grow.
Kubernetes. The default answer, for ecosystem reasons more than technical ones: every vendor, every monitoring tool and every hiring pool assumes it. The cost is real and it is operational, not conceptual. A managed control plane removes most of that cost; a self-managed one replaces your Docker host problem with a harder etcd problem.
Nomad. A single binary that schedules containers, raw binaries, JVM applications and batch jobs with the same model. The right answer when your workload is heterogeneous, or when Kubernetes is more machinery than the problem deserves. Smaller ecosystem, so more will be yours to build.
Two hosts and a load balancer, with no orchestrator. Rarely on anyone’s list and often correct. Two identical Compose stacks behind a health-checked load balancer gives you host redundancy and zero-downtime deploys — drain one, update it, verify, drain the other — with no new control plane to operate. It does not self-heal and it does not autoscale. If you do not need those, you have solved the problem.
The other boundary: who owns this
The technical boundary is about capability. This one is about authority, and it is the boundary that determines how an incident actually goes.
A Docker host has surfaces that different people are responsible for, and the failure mode is a gap between them rather than a gap in any one of them:
| Surface | Typical owner | The question that reveals the gap |
|---|---|---|
| The application in the container | The service team | Who fixes a 500 at 03:00? |
| The image and its base | The service team, usually | Who patches the base image CVE? |
| The Compose file | Contested | Who approves a new published port? |
| The host, kernel, daemon | Platform or infrastructure | Who decides when to reboot? |
| The reverse proxy and certificates | Contested | Who is paged when TLS expires? |
| Backups and restore | Contested | Who has tested the restore? |
The rows marked contested are where incidents lengthen. Not because nobody can fix the problem, but because the first twenty minutes go on working out who should. An expired certificate is a five-minute fix and a ninety-minute outage when the alert routes to a team that does not own the proxy.
# Every Compose project the daemon knows about, and where it came from
docker compose ls --all --format json | jq -r '.[] | [.Name, .Status, .ConfigFiles] | @tsv'
# Any stack whose directory has no OWNERS file is unowned until proven otherwise
docker compose ls --all --format json \
| jq -r '.[].ConfigFiles' | tr ',' '\n' | xargs -r -n1 dirname | sort -u \
| while read -r d; do
[ -f "$d/OWNERS" ] || echo "UNOWNED: $d"
doneRun that on any host that has been in service a couple of years. The output is usually longer than anyone expects, and every line is a thing that will page somebody eventually.
The migration path
Nobody should migrate all at once, and the good news is that nothing you have learned is wasted.
- Compose on one host. Correct for most services, for a long time.
- Compose on two hosts behind a load balancer. Host redundancy and zero-downtime deploys without a control plane. Many services stop here permanently and should.
- An orchestrator, when you need scheduling, self-healing across host failure, or scale that exceeds one machine.
The move from step 2 to step 3 is a change of control plane, not a rewrite.
The images are the same. The healthchecks become readiness and liveness
probes. depends_on becomes init containers and probe-gated rollouts.
Volumes become PersistentVolumeClaims. Compose secrets become Secrets — with
better storage guarantees and the same /run/secrets mount path if you want
it.
That last correspondence is why the discipline in the earlier lessons pays off regardless. A stack with real healthchecks, resource limits, no socket mounts and secrets out of the environment is a stack that ports cleanly. One without them has to be fixed before it can move, and it will be fixed under deadline.
Knowledge check
Knowledge check · 5 questions
Q1. Which requirement genuinely cannot be met by Compose on a single host?
Q2. A team has a deploy script that SSHes to two hosts, holds a lock file, polls health before touching the second host, and has a rollback path. What is the significant risk?
Q3. Which belong in a written operational boundary for a Docker stack? Select all that apply.
Q4. Even inside a single team, surfaces such as certificates, the host reboot window and the restore need a named owner.
Q5. A service needs host redundancy and deploys that do not drop requests, but not scheduling or autoscaling. What is the proportionate answer?
Passing score: 75%. Answers are checked in this browser.