Introduction
Docker Compose has become the standard tool for defining and running multi-container applications. At its heart lies the compose.yaml file—a configuration blueprint that describes how containers interact, what resources they need, and how they are networked. Understanding this file is essential for any developer working with containerized environments. In this article, we’ll break down the structure, key directives, and best practices of compose.yaml, based on fresh insights from a detailed technical review published on Habr (Source).
The Anatomy of compose.yaml
The compose.yaml file (formerly docker-compose.yml) follows the Compose Specification, which defines a standard for describing containerized applications. The basic structure consists of three top-level keys: services, networks, and volumes. However, modern Compose files often include configs, secrets, and x- extensions for advanced use cases. Below is a breakdown of each component.
Services: The Core Building Blocks
Each service represents a containerized component of your application. For example, a web app might have a frontend service, an api service, and a database service. The services section defines how each container is built, run, and connected. Common directives include:
image: Specifies a pre-built image from a registry (e.g.,postgres:16).build: Points to a Dockerfile for building an image locally.ports: Maps host ports to container ports (e.g.,8080:80).environment: Injects environment variables.depends_on: Controls startup order (though it does not wait for readiness).healthcheck: Defines a command to verify container health.
Networks: Connecting Containers
By default, Compose creates a bridge network for all services. You can define custom networks with driver: overlay for Swarm mode or driver: bridge for local development. The networks section allows you to isolate groups of services, improving security and performance. For instance, a frontend service might only connect to api, while api connects to db.
Volumes: Persistent Data
Containers are ephemeral by design. Volumes provide persistent storage for databases, caches, and uploaded files. In compose.yaml, you can define named volumes under the top-level volumes key and mount them into services using the volumes directive. Example:
services:
db:
image: postgres:16
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
Configs and Secrets
For production-grade setups, Compose supports configs (for non-sensitive files like configuration) and secrets (for sensitive data like passwords). These are available only in Docker Swarm mode. The authors note that many developers overlook these features, leading to security vulnerabilities.
Advanced Features and Best Practices
The article highlights several advanced patterns that can make your compose.yaml more robust:
Using YAML Anchors and Extensions
YAML anchors (&) and aliases (*) allow you to reuse blocks of configuration. For example, you can define a common logging configuration and apply it to multiple services:
x-logging: &default-logging
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
services:
app:
image: myapp
logging: *default-logging
worker:
image: myworker
logging: *default-logging
Multi-Stage Builds in Compose
When using build, you can specify a target to use a specific stage in a multi-stage Dockerfile. This is especially useful for separating build and runtime environments. Example:
services:
web:
build:
context: .
target: production
Health Checks and Dependencies
depends_on alone is insufficient for ensuring service readiness. The authors recommend using condition: service_healthy in conjunction with healthcheck. This ensures that a dependent service starts only after the target service passes its health check.
Real-World Examples and Comparisons
Let’s compare a basic compose.yaml for a typical LEMP stack (Linux, Nginx, MySQL, PHP) with an optimized version that includes health checks and volumes:
| Aspect | Basic Version | Optimized Version |
|---|---|---|
| MySQL volume | None (data lost on restart) | Named volume dbdata |
| Nginx healthcheck | Not present | curl http://localhost/health |
| Environment variables | Hardcoded in file | External .env file |
| Network isolation | Default bridge | Custom frontend and backend networks |
Basic Example:
version: "3.8"
services:
web:
image: nginx:alpine
ports:
- "80:80"
app:
image: php:fpm
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: root
Optimized Example:
version: "3.8"
services:
web:
image: nginx:alpine
ports:
- "80:80"
networks:
- frontend
depends_on:
app:
condition: service_healthy
app:
image: php:fpm
networks:
- frontend
- backend
healthcheck:
test: ["CMD", "php-fpm-healthcheck"]
interval: 30s
db:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
volumes:
- dbdata:/var/lib/mysql
networks:
- backend
volumes:
dbdata:
secrets:
db_password:
file: ./secrets/db_password.txt
networks:
frontend:
backend:
Common Pitfalls and How to Avoid Them
The article identifies several mistakes that developers frequently make:
- Hardcoding secrets – Never put passwords in
compose.yaml. Use external.envfiles or Docker secrets. - Ignoring resource limits – Without
deploy.resources, a single container can consume all host memory. - Overusing
depends_onwithout health checks – This leads to race conditions where the dependent service starts before the target is ready. - Using
latesttag – Always pin a specific version for reproducibility.
Conclusion
The compose.yaml file is far more than a simple list of containers. It’s a powerful declarative language for defining application topology, security policies, and deployment rules. By mastering its structure—services, networks, volumes, configs, and secrets—you can build scalable, maintainable, and secure multi-container applications. The Habr article provides an excellent deep dive into these concepts, and we encourage you to read the full piece for more nuanced examples (Source). As containerization continues to evolve, understanding compose.yaml remains a fundamental skill for any DevOps engineer or developer working with Docker.
Comments