Multi-Container Orchestration: Docker Compose Architecture and Declarative Configs
Declarative application definitions, service discovery networks, and depends_on health check sequencing.
Part 17 in Series — Catch up on the previous article: Docker Storage Options: Named Volumes, Bind Mounts, and tmpfs Mount Mechanics (Part 16) before diving into this post.
Onboarding a new software engineer at a fintech startup used to require a 6-page setup wiki.
The engineer had to execute 12 complex docker run commands in exact sequential order, manually creating bridge networks, mounting host directories, allocating ports, and passing 40 environment variables:
docker network create app-net
docker run -d --name db --network app-net -e POSTGRES_PASSWORD=secret postgres:15
docker run -d --name redis --network app-net redis:7-alpine
docker run -d --name worker --network app-net -e DB_HOST=db -e REDIS_HOST=redis my-worker
docker run -d --name api --network app-net -p 8080:8080 -e DB_HOST=db my-api
If the engineer ran the my-api container before the db container finished initializing its database socket, my-api crashed immediately.
When the engineering team introduced Docker Compose:
docker compose up -d
The entire multi-container environment—including databases, caches, message queues, virtual networks, and volume mounts—initialized cleanly in 15 seconds.
How does Docker Compose translate a single declarative YAML file into an orchestrated multi-container architecture?
1. What Docker Compose Is (and Is Not)
Docker Compose is a tool for defining and running multi-container applications on a single host.
IMPERATIVE SHELL SCRIPTS DECLARATIVE COMPOSE SPEC (YAML)
+------------------------------------+ +------------------------------------+
| docker run -d --name db ... | vs | services: |
| docker run -d --name redis ... | | db: { image: postgres:15 } |
| (Prone to typos & order errors) | | web: { build: . , ports: ... } |
+------------------------------------+ +------------------------------------+
- Imperative Approach (
docker run): You manually specify step-by-step commands instructing the engine how to construct resources. - Declarative Approach (
docker-compose.yml): You state what desired state the application environment should possess. Docker Compose inspects the host, compares current state to desired state, and executes necessary changes.
2. Anatomy of a docker-compose.yml File
A Compose specification organizes multi-container applications across three main top-level keys: services, networks, and volumes.
version: '3.8'
services:
# Service 1: PostgreSQL Database
database:
image: postgres:15-alpine
environment:
POSTGRES_DB: app_production
POSTGRES_PASSWORD_FILE: /run/secrets/db_password
volumes:
- db-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
networks:
- backend-net
# Service 2: Node.js API Web Server
web-api:
build:
context: .
dockerfile: Dockerfile
ports:
- "8080:8080"
environment:
DB_HOST: database
depends_on:
database:
condition: service_healthy # Waits for health check readiness!
networks:
- backend-net
networks:
backend-net:
driver: bridge
volumes:
db-data:
3. The depends_on Trap vs Health Check Readiness
A common misunderstanding in Docker Compose involves the depends_on property.
The Problem with Simple depends_on
# INSUFFICIENT:
depends_on:
- database
If you use simple depends_on, Compose guarantees only that the database container process is started before launching web-api.
However, starting a PostgreSQL container process takes 5 milliseconds, while initializing database system tables on disk takes 3 seconds. web-api launches, attempts to connect to port 5432 before PostgreSQL is ready to accept connections, and crashes!
The Solution: depends_on + condition: service_healthy
To enforce true readiness sequencing, pair depends_on with a Container Health Check:
web-api:
depends_on:
database:
condition: service_healthy # Pauses web-api until healthcheck passes!
- Compose launches the
databasecontainer. - Compose executes
pg_isreadyinsidedatabaseevery 5 seconds. - Once PostgreSQL finishes database initialization and
pg_isreadyreturns exit code 0 (healthy), Compose launchesweb-api.
4. Execution Lifecycle: What docker compose up Does
When you run docker compose up -d, the Compose engine executes an automated 6-step lifecycle:
1. Parse docker-compose.yml & validate syntax
|
v
2. Create User-Defined Network (e.g., app_backend-net)
|
v
3. Create & Allocate Named Volumes (e.g., app_db-data)
|
v
4. Build missing container images (if 'build' is specified)
|
v
5. Evaluate dependency tree & launch containers in topological order
|
v
6. Monitor container health & stream logs
Imperative docker run vs Declarative docker compose
| Feature | Imperative docker run | Declarative docker compose |
|---|---|---|
| Configuration Format | Long CLI shell scripts | Single version-controlled docker-compose.yml |
| Service Discovery | Manual network creation required | Automatic isolated network per compose file |
| Startup Order | Manual script execution | Topological dependency resolution (depends_on) |
| Environment Lifecycle | Must manage containers individually | Manage entire stack as one unit (up / down) |
| State Drift Resolution | Re-running script causes container name conflicts | Recreates only modified services automatically |
Summary & Next Steps
Docker Compose simplifies multi-container orchestration for development and single-host deployment:
- Declarative YAML Specifications (
docker-compose.yml) define services, networks, and volumes as version-controlled code. - Automatic User-Defined Networks allow containers within the same Compose stack to resolve each other by service name.
depends_on+condition: service_healthyensures containers wait for dependency readiness rather than just process creation.docker compose upautomates network creation, volume allocation, and container startup in topological order.
In the next article, we inspect Container Hardening: Non-Root Execution, Linux Capabilities, and Read-Only Filesystems.
References & Further Reading
- Center for Internet Security (CIS). (2023). CIS Docker Benchmark v1.6.0 Guidelines. CIS Security.
- Rootless Containers Project. Rootless Containers Architecture and Unprivileged User Namespaces. Rootlesscontaine.rs Docs.
- Docker Inc. Run the Docker Daemon as a Non-Root User (Rootless Mode). Docker Docs.
Part 18: Container Hardening: Non-Root Execution, Linux Capabilities, and Read-Only Filesystems
Continue to Part 18 →