Why Compose
By the end of Chapter 7 the Driftwood stack worked, but only as a fragile sequence of commands: create driftwood-net, create driftwood-db-data, then three docker run invocations in the right order with the right flags, every time, on every machine. Docker Compose replaces that sequence with one declarative file — you describe the desired end state of the whole stack, and docker compose up makes it so.
The shift is from a script you run to a file you keep, version-controlled next to the code it runs. The same compose.yaml that brings the stack up on your laptop brings it up on a teammate's machine and on a small single-host server, with no prose to misread and no order to get wrong.
The Imperative Sequence It Replaces
Running Driftwood by hand is a script with no margin for error. First a docker network create driftwood-net, then a docker volume create driftwood-db-data, then three docker run lines — db with its volume mount and password, web with its database URL and --network, proxy with its 80:80 and 443:443 publish — in a fixed order. One typo in a flag, one skipped --network, one wrong order, and the stack comes up broken in a way that looks like an application bug.
The deeper problem is that nothing records what "right" was. The correct sequence lives in someone's shell history or a README paragraph, and the two drift the moment a flag changes and only one copy gets updated. Compose makes the intended state the artifact, so there is one thing to read and one thing to keep current.
One Declarative File
A compose.yaml states what should exist — three services, one network, one volume — rather than the steps to create them. Compose reads the file, diffs the desired state against what is actually running, and reconciles the difference. That makes up idempotent: run it once and the stack comes up; run it a second time with no changes and nothing happens, because the actual state already matches the desired state.
This is the difference between a recipe and a target. A shell script blindly re-runs its steps and errors when the network already exists; Compose looks at the world, decides what is missing, and creates only that. You stop thinking in commands and start thinking in the end state you want the host to hold.
A Project Is a Unit
Compose groups everything in a file under one project — by default named after the directory the file lives in. The project is the boundary for everything Compose creates: the containers, the networks, the volumes, all labelled as belonging to it. Tear the project down and Compose removes exactly what it made, nothing more.
One project gets one default network, and every service joins it automatically. That single fact removes the manual docker network create from Chapter 7 entirely: web reaches db at the hostname db with no wiring, because Compose put them on the same network and the embedded DNS resolves service names. The plumbing you did by hand becomes a property of the project.
The Core Verbs
Compose operates on the stack, not the individual container. docker compose up brings the whole stack to running; docker compose down tears it down and removes the network it created; docker compose ps shows what is running across the project; docker compose logs aggregates output from every service into one stream. You stop juggling per-container docker commands and act on the project as a single object.
docker runrun lines in a fixed order. The intended state lives only in shell history; one wrong flag breaks the stack silently.docker compose upcompose.yaml states the desired end state. Compose reconciles to it, up is idempotent, and the file is version-controlled next to the code.Where Driftwood Lands
By the end of this chapter the driftwood-net network, the driftwood-db-data volume, and the web, db, and proxy services all live in one compose.yaml checked into the application repo. The page of README setup commands disappears: git clone followed by docker compose up is the entire local setup, and a new contributor has the full stack running in one command.
That is the Layer A finish line. Everything you assembled by hand across Chapters 1 through 7 — images, containers, volumes, networks — collapses into a single declarative file. What it does not do is reach across machines, which is the boundary the next paragraph and the comparison box make explicit.
Raw docker run — imperative steps a human or shell script executes in order. Fine for one container, fragile for a stack, and it keeps no record of the intended state. Reach for it for a single throwaway container or a quick experiment.
Compose — a declarative file for a multi-container stack on one host. It owns the network, the volumes, and the startup of the whole project, and is the right tool for local development and small single-host deployments. This is where Driftwood lives through the end of Layer A.
Kubernetes — declarative too, but it orchestrates containers across many hosts with scheduling, self-healing, and rollout control. Reach for it when one host stops being enough (Chapter 12, topic 76) — not before, because stretching Compose toward it half-builds an orchestrator badly.
- Keeping the hand-run sequence in a README and treating Compose as optional polish — every new contributor re-derives the order and flags from prose, the two drift, and "works on my machine" returns inside the same team.
- Expecting Compose to schedule across machines or restart the stack on a dead host — it manages one host's daemon, and there is no scheduler underneath; multi-host is Kubernetes, and stretching Compose toward it builds a worse one.
- Running
docker compose upfrom the wrong directory and getting a second, empty project — the project name defaults to the directory, so the same stack run from two paths becomes two unrelated networks and two sets of containers. - Mixing hand-run
docker runcontainers with a Compose project on the same host and wondering why they can't reach each other — they sit on different networks; pick one model per stack rather than half of each.
- Commit
compose.yamlnext to the application code so the stack definition versions with the app, andgit clone && docker compose upis the entire local setup. - Use
docker compose up,down,ps, andlogsas the unit of work on the stack instead of per-containerdockercommands, so the project stays internally consistent. - Pin a stable project name with
name:in the file (or-pon the command line) so the stack is identified by intent, not by whatever directory it happened to run from. - Treat Compose as the single-host tool it is — local development and small single-host production — and name the boundary to Kubernetes explicitly when scale forces the move (Chapter 12, topic 76).
docker run scripts the imperative sequence Compose replaces
Podman podman-compose reads the same file; Quadlet expresses it as systemd units
Kubernetes manifests are the multi-host equivalent across the orchestration boundary
Knowledge Check
Why is docker compose up idempotent in a way a shell script of docker run commands is not?
- Compose reconciles the desired state against what's actually running and creates only the difference
- Compose silently ignores any command that returns a non-zero exit and simply continues to the next
- Compose caches the output of each command on the first run and simply replays that cached result instead of executing it again
- A shell script of
docker runlines also reconciles state, so the two behave identically on a re-run
How does a Compose project remove the manual docker network create from Chapter 7?
- Compose auto-creates one default network per project and attaches every service, so names resolve with no manual wiring
- It puts every service directly on the host's network so they all share the host's name resolution
- It connects every container on the machine to one shared global Docker network spanning all projects
- It assigns each service a fixed static IP address on creation that you hard-code into every other service's config instead of using a name
When does a stack outgrow Compose and call for Kubernetes?
- When the stack must run across many hosts with scheduling, self-healing, and rollout control
- As soon as the stack has grown to more than three or four services defined in one file
- As soon as any service in the stack needs to persist its data to a named volume on disk
- As soon as the developers on the team need a fast local edit-and-run feedback loop on their own machines
What does a Compose verb like up or down operate on?
- The whole project as a unit — every service, the network, and the volumes Compose created for it
- A single named container at a time, exactly the same as a plain
dockercommand does - Every container currently running on the host, regardless of which project happens to own it
- Only the images that the services build from, leaving the running containers entirely untouched
You got correct