Getting Started with Docker Compose

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


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.

YAML
# 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

YAML
# 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: ⭐)

YAML
# Minimal docker-compose.yml
services:
  web:
    image: nginx:alpine
    ports:
      - "8080:80"
BASH
# 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: ⭐⭐)

YAML
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:
💡 Tip: 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: ⭐⭐)

YAML
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: ⭐⭐)

YAML
# 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}
BASH
# .env.db
POSTGRES_USER=appuser
POSTGRES_PASSWORD=secret123
📌 Key Point: ${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: ⭐)

BASH
# 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: ⭐)

BASH
# 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: ⭐)

BASH
# 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

YAML
# ============================================
# 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:
BASH
# 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

Q Which version should I use for docker-compose.yml?
A The new Docker Compose V2 no longer requires the 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.
Q Does depends_on guarantee that the service is ready?
A By default, 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.
Q How are environment variables managed in Compose files?
A Three-tier management: ① The 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.
Q Does docker compose down delete data volumes?
A By default, it does not. 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.
Q What is the purpose of the Compose project name?
A The project name isolates the resources of different Compose applications. By default, it uses the name of the current directory. 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


📝 Exercises

  1. Basic Problem (Difficulty ⭐): Write a docker-compose.yml file for the Phase 1 LEMP stack (Nginx+PHP+MySQL) and start it using docker compose up -d.
  2. Advanced Exercise (Difficulty: ⭐⭐): Add depends_on and healthcheck to the compose file to ensure that PHP-FPM starts only after MySQL is ready.
  3. Challenge (Difficulty: ⭐⭐⭐): Use docker compose logs to troubleshoot a service that failed to start, analyze the logs to identify the root cause, and fix the issue.
Web-Tutorial.com

Web-Tutorial Tech Team

A team of developers maintaining programming tutorials. Each tutorial is written and reviewed by developers with expertise in that field. We work to keep our content accurate and reliable — if you spot an issue, please let us know.

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏