The Compose Model: Declaring a Local Stack
Summary
- Compose is a declarative spec for a multi-container application, expressed as one
compose.yamlfile that Docker turns into running services, networks, and volumes. - Insight: Compose is not an orchestrator. It is a convenient front end over the Docker Engine API for a single host, optimized for dev and CI.
- Key Concepts: a service is a definition that produces one or more container instances; a project is the named group Compose manages; networks and volumes are first-class top-level resources.
- When to Use: local development, integration tests, and demos where you want one command to bring up an app plus its backing services.
- Limitations/Trade-offs: no scheduling, no self-healing across hosts, no rolling updates, and no production-grade networking. That is Kubernetes territory.
- Related Topics: the Compose CLI workflow, layering override files, and the gap you cross when you move to manifests.
Foundations
Compose answers one question: how do I describe a whole application, not just a container.
A single docker run command handles one container with flags. Real apps have several containers plus the wiring between them.
The Compose file is that description. It lists each service, the image or build context it comes from, its ports, environment, and dependencies.
The mental model is a desired-state document for one machine. You declare what you want, and Compose reconciles the host to match.
services:
api:
build: .
ports:
- "8080:8080"
depends_on:
- db
db:
image: postgres:17
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:That file is enough to start an API and a database that share a network and persist data. Everything below is elaboration on those three resource types.
Mechanics & Interactions
When you run docker compose up, Compose reads the file, computes the resources it needs, and creates them through the Docker Engine on your host.
It creates a project first. The project name defaults to the directory name and namespaces every resource so two checkouts do not collide.
Each service becomes one container by default, or several if you scale it. The container is named <project>-<service>-<index>.
Compose creates a default bridge network for the project and attaches every service to it. This is the piece that makes local networking feel effortless.
Inside that network, services reach each other by service name. From the api container, the hostname db resolves to the database via Docker's embedded DNS.
That name resolution is the single most useful behavior in the model. You never hardcode IPs; you use the service name as the host.
services:
api:
environment:
DATABASE_URL: "postgres://app:secret@db:5432/app"Here db is not DNS you configured. It is the service name, resolved automatically on the project network.
Volumes are the durable layer. Named volumes survive up and down cycles, so your database keeps its data between restarts.
Bind mounts are the other flavor, mapping a host path into a container. They power live code reload during development.
depends_on controls start order, not readiness. Compose starts db before api, but it does not wait for Postgres to accept connections unless you add a healthcheck condition.
services:
api:
depends_on:
db:
condition: service_healthy
db:
image: postgres:17
healthcheck:
test: ["CMD-SHELL", "pg_isready -U app"]
interval: 5s
retries: 5With a healthcheck and service_healthy, Compose waits for readiness before starting dependents. This closes the most common race in local stacks.
Advanced Considerations & Applications
Compose merges multiple files. By convention it reads compose.yaml then compose.override.yaml, layering the second over the first.
That merge is the basis for environment-specific configuration without duplicating the whole stack. Base defines the topology; overrides adjust it per context.
Build and run are both first-class. A service can reference a prebuilt image or a local build context, and Compose will build on demand.
For CI, Compose is a clean way to stand up dependencies for integration tests, run them, and tear everything down deterministically.
The --build flag rebuilds images before starting, and --wait blocks until services report healthy, which is what you want in a pipeline.
docker compose up --build --wait
docker compose down --volumesThe trade-off is scope. Compose targets one host, so it cannot spread load across machines or reschedule a failed container onto a healthy node.
There is no concept of replicas that survive host failure, no ingress controller, and no secret store beyond files and environment variables.
Treat Compose as the inner development loop. The moment you need multi-host scheduling or zero-downtime rollout, you graduate to Kubernetes.
Common Misconceptions
"Compose runs my app in production." It can run on a single server, but it lacks scheduling, self-healing, and rolling updates. Use it for dev and CI, not resilient production.
"depends_on waits for the service to be ready." It only orders startup. Readiness requires a healthcheck plus condition: service_healthy.
"Compose networking is the same as Kubernetes." Both give name-based discovery, but Compose uses a flat host bridge network with no Services, no policies, and no cluster DNS.
"The container name is stable and predictable." It is derived from the project name, which defaults to the folder. Rename the folder or set a different project and names change.
"Named volumes and bind mounts are interchangeable." Named volumes are Docker-managed and portable; bind mounts tie you to a host path and are meant for live source code.
FAQs
Is docker-compose (with a hyphen) still a thing? That was the v1 Python tool. Current Compose is v2, invoked as docker compose, built into the Docker CLI.
What file name should I use? compose.yaml is the current canonical name. docker-compose.yml still works and is auto-detected for backward compatibility.
Do I need a version: key at the top? No. The Compose Specification dropped the top-level version field; modern Compose ignores it.
How do services find each other? By service name over the project's default network, resolved by Docker's embedded DNS. No IPs required.
Does Compose build my images? Yes, if a service declares a build context. Otherwise it pulls the named image.
Can I run one Compose file against a cluster? Not directly. Compose targets a single Docker host. Moving to a cluster means translating to Kubernetes manifests.
Related
- Compose Basics
- Compose for Local Dev
- Profiles & Overrides
- Compose vs Kubernetes Gap
- Compose Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).