Docker: Getting Started with Docker Compose
Last updated: 2026-08-26
5 docker run commands reduced to 1 docker compose up—Compose transforms multi-container deployment from manual operations to declarative configuration.
1. What You'll Learn
- Structure and Syntax of docker-compose.yml
- Service Definition and Dependency Control
- Declarative Management of Volumes and Networks
- Environment Variable Management Strategies
- Common Docker Compose Commands
2. A Developer’s True Story
(1) Pain Point: Having to run 5 docker run commands every time I debug
Every time Alice debugs, she has to manually type five docker run commands to start the four containers—Web, DB, Cache, Queue, and Worker—which involves many parameters, requires the correct order, and makes it easy to mistype environment variables. Once, she forgot to include --network app-net and spent an hour troubleshooting why the Web container couldn’t connect to the database.
(2) Solutions for Declarative Configuration in Docker Compose
Bob wrote a docker-compose.yml file, so from now on, Alice only needs to docker compose up -d.
# docker-compose.yml - One file defines everything
services:
web:
image: nginx:alpine
ports: ["8080:80"]
depends_on:
- api
api:
build: .
environment:
- DATABASE_URL=postgresql://postgres:secret@db:5432/myapp
db:
image: postgres:15-alpine
environment:
- POSTGRES_PASSWORD=secret
- POSTGRES_DB=myapp
volumes:
- pg-data:/var/lib/postgresql/data
volumes:
pg-data:
networks:
default:
name: app-net
(3) Benefit: 5 commands → 1 command
Deployment has been reduced from 5 commands plus manual configuration to just 1 docker compose up -d. Newcomers can get the entire project up and running in 5 minutes.
3. Structure of docker-compose.yml
(1) The Three Top Fields
# docker-compose.yml structure
services: # Container definitions (required)
web:
image: nginx:alpine
volumes: # Named volume declarations (optional)
pg-data:
networks: # Custom network declarations (optional)
app-net:
(2) docker run vs docker compose Comparison
| Dimension | docker run | docker compose |
|---|---|---|
| Definition Method | Command-line arguments | YAML file |
| Reproducibility | Low (depends on the operator's memory) | High (files can be committed to Git) |
| Multiple containers | Multiple commands | One file |
| Network/Volume | Manual Creation | Automatic Declarative Management |
| Environment variables | Multiple -e arguments | env_file or environment block |
| Version Control | ❌ | ✅ YAML supports git diff |
4. Detailed Explanation of Services Configuration
(1) Common Service Fields
| Field | Purpose | Example |
|---|---|---|
image |
Use an existing image | image: nginx:alpine |
build |
Build from Dockerfile | build: . or build: { context: ., dockerfile: Dockerfile.prod } |
ports |
Port mapping | ports: ["8080:80"] |
environment |
Environment Variables | environment: { POSTGRES_PASSWORD: secret } |
env_file |
Load variables from a file | env_file: .env |
volumes |
Mount volume | volumes: [pg-data:/var/lib/postgresql/data] |
depends_on |
Startup Dependencies | depends_on: [db] |
restart |
Restart Policy | restart: unless-stopped |
networks |
Specified Network | networks: [app-net] |
healthcheck |
Health Checkup | healthcheck: { test: ["CMD", "curl", "-f", "http://localhost/"] } |
▶ Example: Writing the Simplest Compose File (Difficulty: ⭐)
# Minimal docker-compose.yml
services:
web:
image: nginx:alpine
ports:
- "8080:80"
# Start with compose
docker compose up -d
# Verify
docker compose ps
5. Dependency Control: depends_on
(1) Three Waiting Strategies
| Strategy | Syntax | Wait Until | Description |
|---|---|---|---|
| started (default) | depends_on: [db] |
Container started | Service readiness not guaranteed |
| healthy | depends_on: { db: { condition: service_healthy } } |
Health check passed | ✅ Recommended for production use |
| completed | depends_on: { init: { condition: service_completed_successfully } } |
Container exited successfully | Initialization task |
▶ Example: Dependency Control Using depends_on and healthcheck (Difficulty: ⭐⭐)
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: secret
POSTGRES_DB: myapp
volumes:
- pg-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
api:
build: .
environment:
DATABASE_URL: postgresql://postgres:secret@db:5432/myapp
depends_on:
db:
condition: service_healthy
volumes:
pg-data:
depends_on: [db] (default) only waits for the DB container to start; it does not wait for PostgreSQL to be ready. The API may attempt to connect before the DB is ready and fail. condition: service_healthy ensures that the API does not start until PostgreSQL is truly ready.
6. Volumes and Networks Declarations
▶ Example: Declaring Volumes and Networks (Difficulty: ⭐⭐)
services:
web:
image: nginx:alpine
ports:
- "8080:80"
volumes:
- ./src:/usr/share/nginx/html:ro # Bind mount (read-only)
- nginx-cache:/var/cache/nginx # Named volume
networks:
- frontend
- backend
api:
build: .
networks:
- backend
- database
db:
image: postgres:15-alpine
volumes:
- pg-data:/var/lib/postgresql/data
networks:
- database
volumes:
pg-data:
nginx-cache:
networks:
frontend:
backend:
database:
internal: true # No external access
7. Environment Variable Management
(1) Comparison of the Three Methods
| Method | Syntax | Use Cases |
|---|---|---|
| environment block | environment: { KEY: VALUE } |
A small number of fixed variables |
| env_file | env_file: .env |
Multivariate/Sensitive Information |
| Shell Variables | environment: { KEY: ${VAR} } |
Dynamic Configuration |
▶ Example: env_file and variable substitution (Difficulty: ⭐⭐)
# docker-compose.yml with variable substitution
services:
db:
image: postgres:15-alpine
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD:-defaultsecret}
POSTGRES_DB: ${DB_NAME:-myapp}
env_file:
- .env.db
api:
build: .
environment:
DATABASE_URL: postgresql://postgres:${DB_PASSWORD:-defaultsecret}@db:5432/${DB_NAME:-myapp}
# .env.db
POSTGRES_USER=appuser
POSTGRES_PASSWORD=secret123
${VAR:-default} Syntax: If VAR is not set, use the default value. This way, the compose file has default values, which are overridden by the .env file in the production environment.
8. Common Docker Compose Commands
| Command | Function | Example |
|---|---|---|
docker compose up -d |
Start all services (background) | docker compose up -d |
docker compose down |
Stop and delete all containers/networks | docker compose down |
docker compose down -v |
Delete volumes simultaneously | docker compose down -v |
docker compose ps |
View Service Status | docker compose ps |
docker compose logs |
View Log | docker compose logs -f api |
docker compose exec |
Enter Container | docker compose exec api bash |
docker compose build |
Rebuild Image | docker compose build api |
docker compose pull |
Fetch the latest image | docker compose pull |
docker compose config |
Verify and display the merged configuration | docker compose config |
▶ Example: Start with docker compose up -d (Difficulty: ⭐)
# Start all services in detached mode
docker compose up -d
# Follow logs from all services
docker compose logs -f
# Follow logs from a specific service
docker compose logs -f api
▶ Example: Entering a container using docker compose exec (Difficulty: ⭐)
# Open shell in the api service container
docker compose exec api bash
# Run a one-off command
docker compose exec db psql -U postgres -d myapp
▶ Example: docker compose down cleanup (Difficulty: ⭐)
# Stop and remove containers + networks
docker compose down
# Also remove named volumes (WARNING: deletes data)
docker compose down -v
# Also remove images
docker compose down --rmi all
9. Complete Example: WordPress + MySQL
# ============================================
# docker-compose.yml: WordPress + MySQL
# Features: volumes, networks, depends_on, healthcheck
# ============================================
services:
wordpress:
image: wordpress:6.4-php8.2-apache
ports:
- "8080:80"
environment:
WORDPRESS_DB_HOST: mysql
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD:-wppass123}
WORDPRESS_DB_NAME: wordpress
volumes:
- wp-content:/var/www/html/wp-content
depends_on:
mysql:
condition: service_healthy
restart: unless-stopped
networks:
- wp-net
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD:-rootsecret}
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: ${DB_PASSWORD:-wppass123}
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
networks:
- wp-net
volumes:
mysql-data:
wp-content:
networks:
wp-net:
# Deploy
docker compose up -d
# Verify
docker compose ps
docker compose logs -f wordpress
# Access WordPress
# Open http://localhost:8080 in browser
# Clean up (keeps data)
docker compose down
# Clean up everything (deletes data)
docker compose down -v
❓ FAQ
docker-compose.yml?version field. You can start writing directly from services:. If you see version: "3.8" in older tutorials, you can delete it—V2 ignores it.depends_on guarantee that the service is ready?depends_on: [db] only ensures that the db container starts; it does not guarantee that PostgreSQL is ready. You must add condition: service_healthy to wait until the health check passes. This is the most common pitfall for Compose beginners—the service starts, but the connection fails because the dependency is not ready.environment block in docker-compose.yml (non-sensitive default values); ② The .env file (environment-specific values, not committed to Git); ③ The ${VAR:-default} syntax (as a fallback for default values). Do not include sensitive passwords in YAML; use a .env file + .gitignore instead.docker compose down delete data volumes?docker compose down Only containers and networks are deleted; Named Volumes are retained. You must add -v to delete the volumes. Do not use down -v in production environments; use only down.docker compose -p myproject up You can customize the project name. Multiple Compose projects can run on the same machine without interfering with one another.📖 Summary
- docker-compose.yml: Replaces multiple
docker runcommands with declarative YAML - The three main fields: services (container definitions), volumes (persistent volumes), and networks (networks)
depends_on+condition: service_healthyEnsure that dependencies are truly ready- Environment variables: Three-tier management using the
environmentblock,env_file, and${VAR:-default} docker compose up -dOne-click startup,docker compose downOne-click cleanupdocker compose configValidate the syntax of configuration files to avoid discovering errors at runtime
📝 Exercises
- Basic Problem (Difficulty ⭐): Write a
docker-compose.ymlfile for the Phase 1 LEMP stack (Nginx+PHP+MySQL) and start it usingdocker compose up -d. - Advanced Exercise (Difficulty: ⭐⭐): Add
depends_onandhealthcheckto thecomposefile to ensure that PHP-FPM starts only after MySQL is ready. - Challenge (Difficulty: ⭐⭐⭐): Use
docker compose logsto troubleshoot a service that failed to start, analyze the logs to identify the root cause, and fix the issue.