Skip to content

第四阶段:Docker Compose


4.1 Compose 基础

4.1.1 什么是 Docker Compose?

Docker Compose 是一个用于定义和运行多容器 Docker 应用的工具。通过一个 YAML 文件(docker-compose.yml)描述多个服务的配置,然后用一条命令即可创建并启动所有服务。

核心思想:"Define and run multi-container applications" — 用声明式配置取代手动命令。

没有 Compose 的世界:                     有 Compose 的世界:
                                          
# 启动 MySQL                             docker compose up -d
docker run -d \                           # 一条命令搞定一切!
  --name mysql \                          
  --network app-net \                     
  -e MYSQL_ROOT_PASSWORD=123456 \         docker-compose.yml:
  -v mysql-data:/var/lib/mysql \          ┌─────────────────────────┐
  mysql:8.0                               │  services:              │
                                          │    web:                 │
# 启动 Redis                             │      image: nginx       │
docker run -d \                           │      ports: ["80:80"]   │
  --name redis \                          │    mysql:               │
  --network app-net \                     │      image: mysql:8.0   │
  redis:7-alpine                          │      environment: ...   │
                                          │    redis:               │
# 启动 Web 应用                           │      image: redis:7     │
docker run -d \                           └─────────────────────────┘
  --name web \                            
  --network app-net \                     
  -p 80:80 \                              
  -e DB_HOST=mysql \                      
  my-web-app

为什么需要 Docker Compose?

痛点Compose 的解决方案
多个 docker run 命令难记难管理所有配置写在一个 YAML 文件中
容器间网络配置繁琐自动创建网络,服务名即主机名
启动顺序难控制depends_on + healthcheck
环境变量散落在各处统一管理,支持 .env 文件
开发/测试/生产环境不一致配置文件覆盖、profiles

4.1.2 Docker Compose 安装与版本

Docker Compose 有两个版本:

对比docker-compose(V1)docker compose(V2)
安装方式独立 Python 包Docker CLI 插件
命令格式docker-compose updocker compose up(无横杠)
性能Python 实现,较慢Go 实现,更快
维护状态已停止维护当前推荐
bash
# ===== 检查是否已安装(Docker Engine 24.0+ 已内置 Compose V2)=====
docker compose version
# Docker Compose version v2.24.0

# ===== 如果未安装 Compose V2 =====
# 方法一:通过 Docker Engine 安装(推荐,安装 Docker 时已自动安装)
sudo apt-get install docker-compose-plugin

# 方法二:手动安装插件
COMPOSE_VERSION=$(curl -s https://api.github.com/repos/docker/compose/releases/latest | grep '"tag_name"' | cut -d'"' -f4)
sudo curl -L "https://github.com/docker/compose/releases/download/${COMPOSE_VERSION}/docker-compose-$(uname -s)-$(uname -m)" \
  -o /usr/local/lib/docker/cli-plugins/docker-compose
sudo chmod +x /usr/local/lib/docker/cli-plugins/docker-compose

# ===== 验证 =====
docker compose version

⚠️ 注意:本笔记全部使用 V2 命令格式 docker compose(无横杠)。如果你使用的是旧版,将 docker compose 替换为 docker-compose 即可。

4.1.3 docker-compose.yml 文件结构与语法

docker-compose.yml 是一个 YAML 格式的配置文件,包含三个顶级元素:

yaml
# docker-compose.yml 整体结构

# 版本声明(Compose V2 中已可省略,但写上更清晰)
# version: "3.8"  # Compose V2 不再强制要求

# ===== 1. 服务定义(核心)=====
services:
  web:          # 服务名(也是容器在网络中的主机名)
    image: nginx:1.25-alpine
    ports:
      - "80:80"
    # ... 更多配置

  api:
    build: ./api
    ports:
      - "3000:3000"
    depends_on:
      - db
    # ... 更多配置

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
    volumes:
      - db-data:/var/lib/mysql
    # ... 更多配置

# ===== 2. 网络定义 =====
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge

# ===== 3. 数据卷定义 =====
volumes:
  db-data:
    driver: local
三大顶级元素关系图:

┌─────────────────────────────────────────────────────────┐
│                  docker-compose.yml                      │
│                                                          │
│  ┌─────────────── services ─────────────────┐           │
│  │                                           │           │
│  │  ┌─────┐    ┌─────┐    ┌─────┐           │           │
│  │  │ web │◄──►│ api │◄──►│ db  │           │           │
│  │  └──┬──┘    └──┬──┘    └──┬──┘           │           │
│  │     │          │          │               │           │
│  └─────┼──────────┼──────────┼───────────────┘           │
│        │          │          │                            │
│  ┌─────▼──────────▼──────────▼───────────────┐           │
│  │              networks                      │           │
│  │  ┌──────────┐    ┌───────────┐            │           │
│  │  │ frontend │    │  backend  │            │           │
│  │  └──────────┘    └───────────┘            │           │
│  └───────────────────────────────────────────┘           │
│                                                          │
│  ┌───────────── volumes ─────────────────────┐           │
│  │  ┌───────────┐   ┌───────────┐            │           │
│  │  │  db-data  │   │  uploads  │            │           │
│  │  └───────────┘   └───────────┘            │           │
│  └───────────────────────────────────────────┘           │
└─────────────────────────────────────────────────────────┘

4.1.4 services 配置详解

services 是 Compose 的核心,定义了要运行的每一个容器。以下是所有常用配置项:

yaml
services:
  my-service:

    # ========== 镜像来源(二选一)==========
    image: nginx:1.25-alpine          # 直接使用现有镜像

    build:                             # 从 Dockerfile 构建
      context: ./app                   # 构建上下文路径
      dockerfile: Dockerfile.prod      # 指定 Dockerfile(默认 Dockerfile)
      args:                            # 构建参数(对应 Dockerfile 的 ARG)
        APP_VERSION: "1.0"
      target: production               # 多阶段构建的目标阶段

    # ========== 端口映射 ==========
    ports:
      - "8080:80"                      # 宿主机:容器(短格式)
      - "443:443"
      - "127.0.0.1:3000:3000"         # 绑定特定 IP
      - "9090-9091:8080-8081"          # 端口范围

    # 长格式(更明确)
    ports:
      - target: 80                     # 容器端口
        published: 8080                # 宿主机端口
        protocol: tcp                  # 协议
        mode: host                     # host 模式

    # ========== 环境变量 ==========
    environment:                       # 直接定义
      NODE_ENV: production
      DB_HOST: mysql
      DB_PORT: 3306
    # 或数组格式
    environment:
      - NODE_ENV=production
      - DB_HOST=mysql

    env_file:                          # 从文件加载
      - .env
      - ./config/app.env

    # ========== 数据卷挂载 ==========
    volumes:
      - db-data:/var/lib/mysql         # 命名卷(Named Volume)
      - ./src:/app/src                 # 绑定挂载(Bind Mount)
      - ./nginx.conf:/etc/nginx/nginx.conf:ro  # 只读挂载
      - /tmp/cache:/tmp/cache          # 绝对路径绑定

    # 长格式
    volumes:
      - type: volume
        source: db-data
        target: /var/lib/mysql
      - type: bind
        source: ./src
        target: /app/src
        read_only: true

    # ========== 网络 ==========
    networks:
      - frontend
      - backend
    networks:
      backend:
        aliases:                       # 网络别名
          - database
          - db-primary

    # ========== 依赖关系 ==========
    depends_on:
      - db
      - redis
    # 带条件的依赖(推荐)
    depends_on:
      db:
        condition: service_healthy     # 等待 db 健康检查通过
      redis:
        condition: service_started     # 等待 redis 启动

    # ========== 资源限制 ==========
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          cpus: "0.25"
          memory: 128M

    # ========== 重启策略 ==========
    restart: unless-stopped
    # no | always | on-failure | unless-stopped

    # ========== 健康检查 ==========
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s

    # ========== 容器名称 ==========
    container_name: my-custom-name     # 指定容器名(不建议在多实例场景使用)

    # ========== 工作目录 & 命令 ==========
    working_dir: /app
    command: ["python", "app.py"]      # 覆盖 CMD
    entrypoint: ["./entrypoint.sh"]    # 覆盖 ENTRYPOINT

    # ========== 其他 ==========
    stdin_open: true                   # 等同于 docker run -i
    tty: true                          # 等同于 docker run -t
    hostname: my-host                  # 设置主机名
    extra_hosts:                       # 添加 hosts 记录
      - "host.docker.internal:host-gateway"
    logging:                           # 日志配置
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

4.1.5 networks 配置详解

yaml
# Compose 默认行为:自动创建一个名为 <项目名>_default 的 bridge 网络
# 所有服务都连接到这个默认网络,可通过服务名互相访问

# ===== 自定义网络 =====
networks:
  # 简单定义
  frontend:
  backend:

  # 详细配置
  app-network:
    driver: bridge                     # 网络驱动(默认 bridge)
    driver_opts:                       # 驱动选项
      com.docker.network.bridge.name: app-br0
    ipam:                              # IP 地址管理
      driver: default
      config:
        - subnet: 172.28.0.0/16        # 自定义子网
          gateway: 172.28.0.1

  # 使用已有的外部网络
  existing-network:
    external: true                     # 引用已存在的网络
    name: my-existing-network          # 外部网络的名称

# ===== 服务中使用网络 =====
services:
  web:
    image: nginx
    networks:
      - frontend                       # 只连接前端网络

  api:
    image: my-api
    networks:
      - frontend                       # 同时连接前后端网络
      - backend

  db:
    image: mysql
    networks:
      - backend                        # 只连接后端网络
    # web 服务无法直接访问 db(不在同一网络)
网络隔离示意:

     frontend 网络                backend 网络
  ┌────────────────────┐    ┌────────────────────┐
  │                    │    │                    │
  │  ┌─────┐  ┌─────┐ │    │ ┌─────┐  ┌─────┐  │
  │  │ web │  │ api │──┼────┼─│ api │  │ db  │  │
  │  └─────┘  └─────┘ │    │ └─────┘  └─────┘  │
  │                    │    │          ┌───────┐ │
  │     web 可访问 api  │    │          │ redis │ │
  │     web 不可访问 db  │    │          └───────┘ │
  │                    │    │                    │
  └────────────────────┘    └────────────────────┘

4.1.6 volumes 配置详解

yaml
# ===== 顶级 volumes 定义 =====
volumes:
  # 简单定义(Docker 自动管理)
  db-data:
  redis-data:

  # 详细配置
  app-uploads:
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/uploads            # 映射到宿主机指定路径

  # 使用已有的外部卷
  existing-volume:
    external: true
    name: my-existing-volume

# ===== 服务中使用卷 =====
services:
  db:
    image: mysql:8.0
    volumes:
      - db-data:/var/lib/mysql         # 命名卷:数据持久化
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro  # 绑定挂载:初始化脚本

4.1.7 常用命令

bash
# ==================== 启动与停止 ====================

# 启动所有服务(后台模式)
docker compose up -d

# 启动并强制重建镜像
docker compose up -d --build

# 只启动指定服务
docker compose up -d web api

# 停止并移除容器、网络
docker compose down

# 停止并移除容器、网络、卷、镜像
docker compose down -v --rmi all

# 仅停止服务(不移除)
docker compose stop
docker compose stop web              # 只停止指定服务

# 启动已停止的服务
docker compose start

# 重启服务
docker compose restart
docker compose restart api

# ==================== 查看状态 ====================

# 查看服务状态
docker compose ps
docker compose ps -a                 # 包含已停止的

# 查看服务日志
docker compose logs                  # 所有服务日志
docker compose logs -f               # 实时跟踪
docker compose logs -f web api       # 指定服务
docker compose logs --tail 50 web    # 最近 50 行

# 查看服务资源使用
docker compose top                   # 查看进程

# ==================== 执行命令 ====================

# 在运行中的服务内执行命令
docker compose exec web bash         # 进入容器
docker compose exec db mysql -u root -p  # 连接数据库
docker compose exec api npm test     # 运行测试

# 一次性运行命令(启动新容器执行后删除)
docker compose run --rm api npm install
docker compose run --rm web sh -c "echo hello"

# ==================== 构建相关 ====================

# 构建/重建镜像
docker compose build
docker compose build --no-cache      # 不使用缓存
docker compose build api             # 只构建指定服务

# 拉取镜像
docker compose pull

# ==================== 配置与调试 ====================

# 验证并查看最终合并后的配置
docker compose config

# 查看服务的镜像
docker compose images

# 查看服务端口映射
docker compose port web 80

4.2 Compose 进阶

4.2.1 环境变量与 .env 文件

Docker Compose 支持多种方式管理环境变量,按优先级从高到低:

优先级排序(高 → 低):
1. 命令行中的环境变量           docker compose run -e VAR=value
2. Shell 环境中的变量           export VAR=value && docker compose up
3. docker-compose.yml 的 environment 字段
4. env_file 指定的文件
5. Dockerfile 中的 ENV 指令
6. 项目根目录的 .env 文件(仅用于 Compose 文件变量替换)

.env 文件(Compose 变量替换)

bash
# .env 文件 — 位于 docker-compose.yml 同级目录
# 主要用于 Compose 文件自身的变量替换(${ } 语法)

# 项目配置
COMPOSE_PROJECT_NAME=myapp
COMPOSE_FILE=docker-compose.yml

# 版本管理
MYSQL_VERSION=8.0
REDIS_VERSION=7-alpine
NGINX_VERSION=1.25-alpine

# 端口
WEB_PORT=8080
API_PORT=3000
DB_PORT=3306

# 密码(注意:不要提交到 Git)
MYSQL_ROOT_PASSWORD=my-secret-password
MYSQL_DATABASE=myapp
yaml
# docker-compose.yml 中引用 .env 变量
services:
  web:
    image: nginx:${NGINX_VERSION}      # 使用 .env 中的变量
    ports:
      - "${WEB_PORT}:80"

  db:
    image: mysql:${MYSQL_VERSION}
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
    ports:
      - "${DB_PORT}:3306"

env_file(传递给容器的环境变量)

bash
# config/app.env — 传递给容器内部的环境变量
NODE_ENV=production
API_BASE_URL=https://api.example.com
LOG_LEVEL=info
SECRET_KEY=my-app-secret-key
yaml
services:
  api:
    build: ./api
    env_file:
      - ./config/app.env              # 容器内可访问这些变量
      - ./config/db.env
    environment:
      OVERRIDE_VAR: value             # 同名变量会覆盖 env_file 中的

变量替换语法

yaml
services:
  web:
    image: nginx:${NGINX_VERSION:-1.25}    # 默认值:如果未定义则用 1.25
    ports:
      - "${WEB_PORT:?WEB_PORT must be set}:80"  # 必填:未定义则报错
    environment:
      DEBUG: ${DEBUG:-false}                # 默认 false
      HOST: ${HOST:-0.0.0.0}

4.2.2 服务依赖与启动顺序

基础 depends_on

yaml
services:
  web:
    build: ./web
    depends_on:
      - api                            # web 在 api 之后启动

  api:
    build: ./api
    depends_on:
      - db                             # api 在 db 之后启动
      - redis

  db:
    image: mysql:8.0

  redis:
    image: redis:7-alpine

# 启动顺序:db + redis(并行)→ api → web
# 停止顺序:反过来 web → api → db + redis

⚠️ 重要限制depends_on 只保证启动顺序,不保证服务就绪。MySQL 容器启动了不代表 MySQL 服务可以接受连接!

depends_on + healthcheck(推荐方案)

yaml
services:
  web:
    build: ./web
    depends_on:
      api:
        condition: service_healthy     # 等 api 健康检查通过再启动

  api:
    build: ./api
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    depends_on:
      db:
        condition: service_healthy     # 等 db 健康检查通过
      redis:
        condition: service_started     # redis 启动即可

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s               # MySQL 初始化需要时间

  redis:
    image: redis:7-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
带 healthcheck 的启动流程:

时间 ──────────────────────────────────────────────────▶

db:     [启动]──────[初始化中...]──────[healthy ✅]
redis:  [启动]───[healthy ✅]
api:                                   [启动]──────[healthy ✅]
web:                                                           [启动]

常见服务的 healthcheck 模板

yaml
# MySQL
healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
  interval: 10s
  timeout: 5s
  retries: 10
  start_period: 30s

# PostgreSQL
healthcheck:
  test: ["CMD-SHELL", "pg_isready -U postgres"]
  interval: 10s
  timeout: 5s
  retries: 10

# Redis
healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 10s
  timeout: 5s
  retries: 5

# MongoDB
healthcheck:
  test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"]
  interval: 10s
  timeout: 5s
  retries: 10

# Elasticsearch
healthcheck:
  test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
  interval: 30s
  timeout: 10s
  retries: 10

# HTTP 服务通用
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 10s

4.2.3 多配置文件覆盖

Docker Compose 支持多个配置文件的合并,实现不同环境的差异化配置。

配置文件合并机制:

docker-compose.yml          → 基础配置(通用)
     ↓ 合并
docker-compose.override.yml → 覆盖配置(默认自动加载)
     ↓ 合并
docker-compose.prod.yml     → 生产环境覆盖(手动指定)

基础配置

yaml
# docker-compose.yml — 基础通用配置
services:
  web:
    build: ./web
    ports:
      - "80:80"

  api:
    build: ./api
    environment:
      NODE_ENV: production
    depends_on:
      - db

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: myapp
    volumes:
      - db-data:/var/lib/mysql

volumes:
  db-data:

开发环境覆盖

yaml
# docker-compose.override.yml — 开发环境(自动加载,无需 -f 参数)
services:
  web:
    volumes:
      - ./web/src:/app/src             # 挂载源码,热重载
    environment:
      DEBUG: "true"

  api:
    volumes:
      - ./api/src:/app/src             # 挂载源码
    environment:
      NODE_ENV: development
      DEBUG: "true"
    command: ["npm", "run", "dev"]     # 覆盖为开发模式启动
    ports:
      - "3000:3000"
      - "9229:9229"                    # 调试端口

  db:
    ports:
      - "3306:3306"                    # 开发环境暴露数据库端口

生产环境覆盖

yaml
# docker-compose.prod.yml — 生产环境
services:
  web:
    restart: always
    deploy:
      resources:
        limits:
          memory: 256M

  api:
    restart: always
    environment:
      NODE_ENV: production
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
      replicas: 2                      # 启动 2 个实例

  db:
    restart: always
    # 生产环境不暴露端口
    deploy:
      resources:
        limits:
          memory: 1G

使用方式

bash
# 开发环境(自动加载 override)
docker compose up -d
# 等同于:docker compose -f docker-compose.yml -f docker-compose.override.yml up -d

# 生产环境(手动指定,跳过 override)
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# 查看合并后的最终配置
docker compose -f docker-compose.yml -f docker-compose.prod.yml config

# 使用环境变量指定配置文件
export COMPOSE_FILE=docker-compose.yml:docker-compose.prod.yml
docker compose up -d

4.2.4 Compose Profiles

Profiles 允许你将服务分组,按需启动不同的服务集合。

yaml
services:
  # ===== 核心服务(无 profile,始终启动)=====
  web:
    image: nginx
    ports:
      - "80:80"

  api:
    build: ./api
    ports:
      - "3000:3000"

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: secret

  # ===== 调试工具(debug profile)=====
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    profiles:
      - debug                         # 只在 debug profile 下启动

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"
    profiles:
      - debug

  # ===== 监控工具(monitoring profile)=====
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    profiles:
      - monitoring

  grafana:
    image: grafana/grafana
    ports:
      - "3001:3000"
    profiles:
      - monitoring

  # ===== 测试工具(test profile)=====
  test-runner:
    build: ./api
    command: ["npm", "test"]
    profiles:
      - test
    depends_on:
      - db
bash
# 只启动核心服务(无 profile 的服务)
docker compose up -d

# 启动核心服务 + 调试工具
docker compose --profile debug up -d

# 启动核心服务 + 监控
docker compose --profile monitoring up -d

# 启动多个 profile
docker compose --profile debug --profile monitoring up -d

# 运行测试
docker compose --profile test run --rm test-runner

# 使用环境变量
export COMPOSE_PROFILES=debug
docker compose up -d

4.2.5 服务扩缩容

bash
# 将 api 服务扩展为 3 个实例
docker compose up -d --scale api=3

# 查看扩展后的实例
docker compose ps
# NAME         SERVICE   STATUS    PORTS
# app-api-1    api       Up        0.0.0.0:3000->3000/tcp
# app-api-2    api       Up        0.0.0.0:3001->3000/tcp
# app-api-3    api       Up        0.0.0.0:3002->3000/tcp
# app-web-1    web       Up        0.0.0.0:80->80/tcp

# 缩容
docker compose up -d --scale api=1

⚠️ 扩容注意事项

  1. 不能使用 container_name(多实例名称冲突)
  2. 不能使用固定的宿主机端口映射,应使用端口范围或让 Docker 自动分配
  3. 通常需要配合负载均衡器(如 Nginx)使用
yaml
# 支持扩容的配置写法
services:
  api:
    build: ./api
    # ❌ 不能用固定端口
    # ports:
    #   - "3000:3000"

    # ✅ 不映射端口,通过 Nginx 代理
    expose:
      - "3000"

    # ❌ 不能指定容器名
    # container_name: my-api

  nginx:
    image: nginx:1.25-alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
    depends_on:
      - api
nginx
# nginx.conf — 负载均衡配置
upstream api_servers {
    # Docker Compose 的 DNS 轮询会自动解析所有 api 实例
    server api:3000;
}

server {
    listen 80;

    location / {
        proxy_pass http://api_servers;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

4.2.6 Compose Watch(开发热重载)

Compose V2.22+ 引入了 watch 功能,提供更智能的开发体验:

yaml
services:
  api:
    build: ./api
    ports:
      - "3000:3000"
    develop:
      watch:
        # 源码变更 → 同步到容器(热重载)
        - action: sync
          path: ./api/src
          target: /app/src

        # 依赖文件变更 → 重建镜像并重启
        - action: rebuild
          path: ./api/package.json

        # 配置文件变更 → 重启服务
        - action: sync+restart
          path: ./api/config
          target: /app/config
bash
# 启动 watch 模式
docker compose watch

# 或后台启动
docker compose up -d
docker compose watch &

4.3 实战项目

Demo 1:最小 Compose 入门 — Nginx + 静态页面

yaml
# demo1-nginx/docker-compose.yml
services:
  web:
    image: nginx:1.25-alpine
    ports:
      - "8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro
bash
# 创建项目
mkdir -p demo1-nginx/html && cd demo1-nginx

# 创建静态页面
cat > html/index.html <<'EOF'
<!DOCTYPE html>
<html>
<head>
    <title>Docker Compose Demo</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            margin: 0;
            background: linear-gradient(135deg, #0093E9 0%, #80D0C7 100%);
            color: white;
        }
        .container { text-align: center; }
        h1 { font-size: 2.5em; }
    </style>
</head>
<body>
    <div class="container">
        <h1>🐳 Docker Compose is Working!</h1>
        <p>Served by Nginx in a Docker container</p>
    </div>
</body>
</html>
EOF

# 启动
docker compose up -d

# 测试
curl http://localhost:8080

# 查看状态
docker compose ps

# 查看日志
docker compose logs -f

# 停止
docker compose down

Demo 2:Python Flask + Redis 计数器

demo2-flask-redis/
├── docker-compose.yml
├── app/
│   ├── app.py
│   ├── requirements.txt
│   └── Dockerfile
yaml
# demo2-flask-redis/docker-compose.yml
services:
  web:
    build: ./app
    ports:
      - "5000:5000"
    environment:
      REDIS_HOST: redis
      REDIS_PORT: 6379
    depends_on:
      redis:
        condition: service_healthy
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  redis-data:
python
# demo2-flask-redis/app/app.py
import os
import socket
from flask import Flask, jsonify
import redis

app = Flask(__name__)

redis_client = redis.Redis(
    host=os.getenv("REDIS_HOST", "localhost"),
    port=int(os.getenv("REDIS_PORT", 6379)),
    decode_responses=True,
)

@app.route("/")
def index():
    count = redis_client.incr("visit_count")
    return jsonify({
        "message": "Hello from Docker Compose!",
        "visit_count": count,
        "hostname": socket.gethostname(),
    })

@app.route("/health")
def health():
    try:
        redis_client.ping()
        return jsonify({"status": "healthy", "redis": "connected"}), 200
    except redis.ConnectionError:
        return jsonify({"status": "unhealthy", "redis": "disconnected"}), 503

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=5000)
# demo2-flask-redis/app/requirements.txt
flask==3.0.0
redis==5.0.0
gunicorn==21.2.0
dockerfile
# demo2-flask-redis/app/Dockerfile
FROM python:3.12-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY . .

RUN useradd -r -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

EXPOSE 5000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
    CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')"

CMD ["gunicorn", "app:app", "--bind", "0.0.0.0:5000", "--workers", "2"]
bash
# 启动
cd demo2-flask-redis
docker compose up -d --build

# 测试(多次访问,观察计数器递增)
curl http://localhost:5000
# {"hostname":"abc123","message":"Hello from Docker Compose!","visit_count":1}
curl http://localhost:5000
# {"hostname":"abc123","message":"Hello from Docker Compose!","visit_count":2}

# 进入 Redis 查看数据
docker compose exec redis redis-cli
> GET visit_count
> "2"
> exit

# 重启服务后数据不丢失(Redis 持久化到 Volume)
docker compose restart
curl http://localhost:5000
# {"visit_count":3}   ← 继续递增,数据未丢失

# 清理
docker compose down        # 保留 volume
docker compose down -v     # 清除 volume(数据丢失)

Demo 3:完整 Web 应用栈 — Nginx + Node.js API + MySQL + Redis

这是学习计划中要求的综合实战项目

demo3-fullstack/
├── docker-compose.yml
├── .env
├── nginx/
│   └── nginx.conf
├── api/
│   ├── src/
│   │   └── index.js
│   ├── package.json
│   └── Dockerfile
└── db/
    └── init.sql
bash
# .env
COMPOSE_PROJECT_NAME=fullstack-app

# Versions
MYSQL_VERSION=8.0
REDIS_VERSION=7-alpine
NGINX_VERSION=1.25-alpine
NODE_VERSION=20-alpine

# Ports
WEB_PORT=80
API_PORT=3000

# Database
MYSQL_ROOT_PASSWORD=rootpassword123
MYSQL_DATABASE=fullstack_db
MYSQL_USER=appuser
MYSQL_PASSWORD=apppassword123
yaml
# demo3-fullstack/docker-compose.yml
services:

  # ===== Nginx 反向代理 =====
  nginx:
    image: nginx:${NGINX_VERSION}
    ports:
      - "${WEB_PORT}:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
    depends_on:
      api:
        condition: service_healthy
    restart: unless-stopped
    networks:
      - frontend

  # ===== Node.js API =====
  api:
    build:
      context: ./api
      dockerfile: Dockerfile
    expose:
      - "3000"
    environment:
      NODE_ENV: production
      DB_HOST: mysql
      DB_PORT: 3306
      DB_USER: ${MYSQL_USER}
      DB_PASSWORD: ${MYSQL_PASSWORD}
      DB_NAME: ${MYSQL_DATABASE}
      REDIS_HOST: redis
      REDIS_PORT: 6379
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:3000/health"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 15s
    restart: unless-stopped
    networks:
      - frontend
      - backend

  # ===== MySQL 数据库 =====
  mysql:
    image: mysql:${MYSQL_VERSION}
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s
    restart: unless-stopped
    networks:
      - backend

  # ===== Redis 缓存 =====
  redis:
    image: redis:${REDIS_VERSION}
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    networks:
      - backend

networks:
  frontend:
  backend:

volumes:
  mysql-data:
  redis-data:
nginx
# demo3-fullstack/nginx/nginx.conf
upstream api_backend {
    server api:3000;
}

server {
    listen 80;
    server_name localhost;

    # API 请求代理
    location /api/ {
        proxy_pass http://api_backend/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # 健康检查
    location /health {
        proxy_pass http://api_backend/health;
    }

    # 默认首页
    location / {
        default_type text/html;
        return 200 '<!DOCTYPE html>
<html>
<head>
    <title>Fullstack App</title>
    <style>
        body { font-family: sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
        h1 { color: #0066cc; }
        pre { background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto; }
        button { padding: 10px 20px; font-size: 16px; cursor: pointer;
                 background: #0066cc; color: white; border: none; border-radius: 5px; margin: 5px; }
        button:hover { background: #0052a3; }
        #result { margin-top: 20px; }
    </style>
</head>
<body>
    <h1>Docker Compose Fullstack Demo</h1>
    <p>Nginx + Node.js + MySQL + Redis</p>
    <button onclick="fetchHealth()">Health Check</button>
    <button onclick="fetchUsers()">Get Users</button>
    <button onclick="addUser()">Add Random User</button>
    <button onclick="fetchStats()">Get Stats</button>
    <div id="result"><pre>Click a button to test the API...</pre></div>
    <script>
        async function fetchData(url) {
            try {
                const res = await fetch(url);
                const data = await res.json();
                document.getElementById("result").innerHTML = "<pre>" + JSON.stringify(data, null, 2) + "</pre>";
            } catch(e) {
                document.getElementById("result").innerHTML = "<pre>Error: " + e.message + "</pre>";
            }
        }
        function fetchHealth() { fetchData("/api/health"); }
        function fetchUsers() { fetchData("/api/users"); }
        function fetchStats() { fetchData("/api/stats"); }
        async function addUser() {
            const names = ["Alice","Bob","Charlie","Diana","Eve","Frank","Grace","Henry"];
            const name = names[Math.floor(Math.random() * names.length)];
            const res = await fetch("/api/users", {
                method: "POST",
                headers: {"Content-Type": "application/json"},
                body: JSON.stringify({ name: name, email: name.toLowerCase() + Math.floor(Math.random()*1000) + "@example.com" })
            });
            const data = await res.json();
            document.getElementById("result").innerHTML = "<pre>" + JSON.stringify(data, null, 2) + "</pre>";
        }
    </script>
</body>
</html>';
    }
}
sql
-- demo3-fullstack/db/init.sql
CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

INSERT INTO users (name, email) VALUES
    ('Admin', 'admin@example.com'),
    ('Test User', 'test@example.com');
json
// demo3-fullstack/api/package.json
{
  "name": "fullstack-api",
  "version": "1.0.0",
  "main": "src/index.js",
  "dependencies": {
    "express": "^4.18.2",
    "mysql2": "^3.6.5",
    "redis": "^4.6.11"
  }
}
javascript
// demo3-fullstack/api/src/index.js
const express = require("express");
const mysql = require("mysql2/promise");
const { createClient } = require("redis");

const app = express();
app.use(express.json());

const PORT = process.env.PORT || 3000;

const pool = mysql.createPool({
  host: process.env.DB_HOST || "localhost",
  port: parseInt(process.env.DB_PORT || "3306"),
  user: process.env.DB_USER || "root",
  password: process.env.DB_PASSWORD || "",
  database: process.env.DB_NAME || "test",
  waitForConnections: true,
  connectionLimit: 10,
});

const redisClient = createClient({
  url: `redis://${process.env.REDIS_HOST || "localhost"}:${process.env.REDIS_PORT || 6379}`,
});

redisClient.on("error", (err) => console.error("Redis error:", err));

(async () => {
  await redisClient.connect();
  console.log("Connected to Redis");
})();

app.get("/health", async (req, res) => {
  try {
    await pool.query("SELECT 1");
    await redisClient.ping();
    res.json({
      status: "healthy",
      services: { mysql: "connected", redis: "connected" },
      timestamp: new Date().toISOString(),
    });
  } catch (err) {
    res.status(503).json({
      status: "unhealthy",
      error: err.message,
    });
  }
});

app.get("/users", async (req, res) => {
  try {
    const cached = await redisClient.get("users:all");
    if (cached) {
      return res.json({ source: "cache", users: JSON.parse(cached) });
    }

    const [rows] = await pool.query("SELECT * FROM users ORDER BY created_at DESC");
    await redisClient.setEx("users:all", 30, JSON.stringify(rows));
    res.json({ source: "database", users: rows });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.post("/users", async (req, res) => {
  try {
    const { name, email } = req.body;
    if (!name || !email) {
      return res.status(400).json({ error: "name and email are required" });
    }
    const [result] = await pool.query(
      "INSERT INTO users (name, email) VALUES (?, ?)",
      [name, email]
    );
    await redisClient.del("users:all");
    res.status(201).json({ id: result.insertId, name, email });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.get("/stats", async (req, res) => {
  try {
    const visits = await redisClient.incr("api:visits");
    const [userCount] = await pool.query("SELECT COUNT(*) as count FROM users");
    res.json({
      api_visits: visits,
      total_users: userCount[0].count,
      uptime: process.uptime().toFixed(0) + "s",
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(PORT, "0.0.0.0", () => {
  console.log(`API server running on port ${PORT}`);
});

process.on("SIGTERM", async () => {
  console.log("Shutting down gracefully...");
  await redisClient.quit();
  await pool.end();
  process.exit(0);
});
dockerfile
# demo3-fullstack/api/Dockerfile
FROM node:20-alpine

ENV NODE_ENV=production

WORKDIR /app

COPY package.json package-lock.json* ./
RUN npm install --omit=dev && npm cache clean --force

COPY . .

USER node

EXPOSE 3000

CMD ["node", "src/index.js"]
bash
# ===== 启动完整应用栈 =====
cd demo3-fullstack

# 构建并启动
docker compose up -d --build

# 查看服务状态
docker compose ps

# 等待所有服务健康(观察 STATUS 列)
watch -n 2 docker compose ps

# ===== 测试 =====

# 访问首页
curl http://localhost

# 健康检查
curl http://localhost/api/health

# 获取用户列表
curl http://localhost/api/users

# 添加用户
curl -X POST http://localhost/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Docker Fan", "email": "docker@example.com"}'

# 再次获取(观察 source 从 database 变为 cache)
curl http://localhost/api/users
# → {"source":"cache", ...}   ← Redis 缓存命中!

# 查看统计
curl http://localhost/api/stats

# ===== 运维操作 =====

# 查看日志
docker compose logs -f api

# 进入 MySQL 交互
docker compose exec mysql mysql -u appuser -papppassword123 fullstack_db
# mysql> SELECT * FROM users;
# mysql> exit

# 进入 Redis 交互
docker compose exec redis redis-cli
# > KEYS *
# > GET users:all
# > exit

# ===== 清理 =====
docker compose down           # 保留数据卷
docker compose down -v        # 清除所有数据

Demo 4:开发环境 vs 生产环境多配置

demo4-multi-env/
├── docker-compose.yml              # 基础配置
├── docker-compose.override.yml     # 开发环境(自动加载)
├── docker-compose.prod.yml         # 生产环境
├── .env                            # 环境变量
└── api/
    ├── src/index.js
    ├── package.json
    └── Dockerfile
yaml
# docker-compose.yml — 基础配置
services:
  api:
    build: ./api
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD:-password}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
      interval: 10s
      timeout: 5s
      retries: 10

volumes:
  db-data:
yaml
# docker-compose.override.yml — 开发环境
services:
  api:
    ports:
      - "3000:3000"
      - "9229:9229"                    # Node.js 调试端口
    volumes:
      - ./api/src:/app/src             # 热重载
    environment:
      NODE_ENV: development
      DEBUG: "true"
    command: ["node", "--watch", "--inspect=0.0.0.0:9229", "src/index.js"]

  db:
    ports:
      - "5432:5432"                    # 暴露数据库端口便于本地工具连接

  adminer:                             # 开发环境独有的数据库管理工具
    image: adminer
    ports:
      - "8080:8080"
    depends_on:
      - db
yaml
# docker-compose.prod.yml — 生产环境
services:
  api:
    ports:
      - "3000:3000"
    environment:
      NODE_ENV: production
    restart: always
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M

  db:
    restart: always
    # 生产不暴露端口
    deploy:
      resources:
        limits:
          memory: 1G
bash
# 开发环境:自动加载 override
docker compose up -d
# 包含:api(带调试端口+热重载)+ db(暴露端口)+ adminer

# 生产环境:跳过 override,加载 prod
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
# 包含:api(生产模式+资源限制)+ db(不暴露端口)
# 不包含 adminer

# 验证配置
docker compose config                    # 查看开发配置
docker compose -f docker-compose.yml -f docker-compose.prod.yml config  # 查看生产配置

Demo 5:使用 Profiles 管理可选服务

yaml
# demo5-profiles/docker-compose.yml
services:
  # 核心服务 — 始终启动
  app:
    image: nginx:alpine
    ports:
      - "80:80"

  # 数据库管理工具 — debug profile
  adminer:
    image: adminer
    ports:
      - "8080:8080"
    profiles: ["debug"]

  # 监控 — monitoring profile
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    profiles: ["monitoring"]

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    profiles: ["monitoring"]

  # 日志 — logging profile
  loki:
    image: grafana/loki:latest
    ports:
      - "3100:3100"
    profiles: ["logging"]
bash
# 只启动核心
docker compose up -d

# 核心 + 调试
docker compose --profile debug up -d

# 核心 + 监控 + 日志
docker compose --profile monitoring --profile logging up -d

# 查看运行的服务
docker compose ps

# 停止特定 profile 的服务
docker compose --profile debug down

4.4 常见问题 QA

Q1: docker compose updocker compose run 有什么区别?

对比docker compose updocker compose run
作用启动整个应用(所有服务)在一个服务中运行一次性命令
依赖启动所有依赖默认启动依赖(可用 --no-deps 跳过)
端口映射 YAML 中定义的端口默认不映射端口(可用 -p 指定)
容器生命周期保持运行命令完成后退出
使用场景启动应用运行迁移、测试、一次性脚本
bash
# up:启动整个应用
docker compose up -d

# run:运行一次性命令
docker compose run --rm api npm test           # 运行测试
docker compose run --rm api npm run migrate    # 数据库迁移
docker compose run --rm api sh                 # 进入 shell
docker compose run --rm --no-deps api node -e "console.log('hello')"  # 不启动依赖

Q2: docker compose execdocker compose run 有什么区别?

对比docker compose execdocker compose run
目标已运行的容器中执行命令启动新容器执行命令
前提服务必须已经在运行无需服务运行
容器复用现有容器创建新容器
环境共享运行中容器的环境独立的新容器环境
bash
# exec:在运行中的容器执行(服务必须已启动)
docker compose exec db mysql -u root -p
docker compose exec api bash

# run:启动新容器执行(即使服务未启动也可以)
docker compose run --rm api npm install
docker compose run --rm db mysql -u root -p

Q3: 容器启动后报错 Connection refused(连接被拒绝)

原因:依赖的服务(如数据库)还没完全启动就尝试连接。

解决方案

yaml
# 方案一:使用 depends_on + healthcheck(推荐)
services:
  api:
    depends_on:
      db:
        condition: service_healthy    # 等 db 健康才启动 api

  db:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s
bash
# 方案二:应用层重试连接
# 在应用代码中加入连接重试逻辑(更健壮)
javascript
// Node.js 连接重试示例
async function connectWithRetry(maxRetries = 10, delay = 3000) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      await pool.query("SELECT 1");
      console.log("Database connected!");
      return;
    } catch (err) {
      console.log(`DB connection attempt ${i + 1}/${maxRetries} failed, retrying in ${delay}ms...`);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
  throw new Error("Failed to connect to database after max retries");
}

Q4: docker compose down 后数据丢失了

原因:使用了 docker compose down -v-v 参数会删除命名卷。

bash
# ===== 数据保留行为 =====

# 保留卷(数据不丢失)
docker compose down

# 删除卷(⚠️ 数据丢失!)
docker compose down -v

# 删除卷和镜像
docker compose down -v --rmi all

# ===== 最佳实践 =====
# 生产环境:永远不要用 -v
# 开发环境:需要重置数据时才用 -v

# 查看卷
docker volume ls | grep <project-name>

# 手动备份卷数据
docker run --rm \
  -v fullstack-app_mysql-data:/data \
  -v $(pwd):/backup \
  alpine tar czf /backup/mysql-backup.tar.gz -C /data .

Q5: 如何让容器访问宿主机的服务(如本地数据库)?

yaml
services:
  api:
    image: my-api
    extra_hosts:
      # 添加特殊主机名,指向宿主机
      - "host.docker.internal:host-gateway"
    environment:
      # 使用 host.docker.internal 替代 localhost
      DB_HOST: host.docker.internal
      DB_PORT: 5432
bash
# Linux 上 host.docker.internal 需要 Docker 20.10+
# 或者使用 network_mode: host(容器共享宿主机网络栈)

Q6: docker compose up --builddocker compose build + docker compose up 有区别吗?

bash
# 方式一:分开执行
docker compose build       # 构建镜像
docker compose up -d       # 启动服务(使用已构建的镜像)

# 方式二:一步到位
docker compose up -d --build   # 构建 + 启动

# 区别不大,但 --build 更方便
# 注意:如果没有代码变更,build 会使用缓存,不会重新构建

# 强制不使用缓存重建
docker compose build --no-cache
docker compose up -d

Q7: 多个 Compose 项目端口冲突怎么办?

bash
# 错误:两个项目都映射了 80 端口
# 项目 A:ports: - "80:80"
# 项目 B:ports: - "80:80"
# → Bind for 0.0.0.0:80 failed: port is already allocated

# ===== 解决方案 =====

# 方案一:使用不同端口
# 项目 A:ports: - "8080:80"
# 项目 B:ports: - "8081:80"

# 方案二:使用 .env 文件管理端口
# 项目 A 的 .env: WEB_PORT=8080
# 项目 B 的 .env: WEB_PORT=8081

# 方案三:查看端口占用
docker compose ps --format "table {{.Name}}\t{{.Ports}}"
# 或
docker ps --format "table {{.Names}}\t{{.Ports}}" | grep "0.0.0.0"

Q8: 如何在 Compose 中使用已有的外部网络/卷?

yaml
# 使用已存在的外部网络(跨 Compose 项目通信)
networks:
  shared-network:
    external: true                     # 引用外部网络
    name: my-shared-network            # 外部网络名称

# 使用已存在的外部卷
volumes:
  shared-data:
    external: true
    name: my-shared-volume
bash
# 先创建外部网络/卷
docker network create my-shared-network
docker volume create my-shared-volume

# 然后在 Compose 中使用
docker compose up -d

Q9: Compose 文件中的相对路径是相对于什么?

yaml
services:
  api:
    build:
      context: ./api                   # 相对于 docker-compose.yml 文件的位置
    volumes:
      - ./src:/app/src                 # 相对于 docker-compose.yml 文件的位置
    env_file:
      - ./config/.env                  # 相对于 docker-compose.yml 文件的位置

# ⚠️ 所有相对路径都是相对于 docker-compose.yml 文件所在目录
# 而不是执行 docker compose 命令时的目录

# 如果从其他目录执行:
# cd /somewhere/else
# docker compose -f /path/to/docker-compose.yml up
# → 路径仍然相对于 /path/to/

Q10: 如何查看 Compose 生成的资源名称?

bash
# Compose 默认的命名规则:<项目名>_<资源名>

# 项目名默认是 docker-compose.yml 所在的目录名
# 可通过 .env 中的 COMPOSE_PROJECT_NAME 或 -p 参数覆盖

# 查看所有资源
docker compose ps                    # 容器
docker network ls | grep <project>   # 网络
docker volume ls | grep <project>    # 卷

# 自定义项目名
docker compose -p myproject up -d

# 或在 .env 中设置
# COMPOSE_PROJECT_NAME=myproject

# 命名示例:
# 目录名为 demo3-fullstack,则:
# 容器:demo3-fullstack-api-1, demo3-fullstack-mysql-1
# 网络:demo3-fullstack_frontend, demo3-fullstack_backend
# 卷:  demo3-fullstack_mysql-data

Q11: docker compose 命令要在哪个目录执行?

bash
# 默认情况:在 docker-compose.yml 所在的目录执行
cd /path/to/project
docker compose up -d

# 在其他目录执行:使用 -f 指定文件路径
docker compose -f /path/to/docker-compose.yml up -d

# 使用环境变量指定
export COMPOSE_FILE=/path/to/docker-compose.yml
docker compose up -d

Q12: 如何优雅地更新单个服务(不影响其他服务)?

bash
# 只重建并重启指定服务
docker compose up -d --build api

# 不重建,仅重启
docker compose restart api

# 拉取最新镜像并重启
docker compose pull nginx
docker compose up -d nginx

# 强制重建(即使没有代码变更)
docker compose up -d --force-recreate api
# 或者
docker compose build --no-cache api
docker compose up -d api

4.5 Compose 命令速查表

┌──────────────────────────────────────────────────────────────────┐
│                 Docker Compose 命令速查表                         │
├──────────────────┬───────────────────────────────────────────────┤
│                  │                                               │
│  启动与停止       │  docker compose up -d        后台启动          │
│                  │  docker compose up -d --build 构建并启动       │
│                  │  docker compose down          停止并清理       │
│                  │  docker compose down -v       清理含数据卷     │
│                  │  docker compose stop          仅停止           │
│                  │  docker compose start         启动已停止服务    │
│                  │  docker compose restart       重启             │
│                  │                                               │
├──────────────────┼───────────────────────────────────────────────┤
│                  │                                               │
│  查看状态         │  docker compose ps            服务状态         │
│                  │  docker compose logs -f       实时日志         │
│                  │  docker compose top           查看进程         │
│                  │  docker compose images        查看镜像         │
│                  │  docker compose port svc 80   查看端口映射     │
│                  │                                               │
├──────────────────┼───────────────────────────────────────────────┤
│                  │                                               │
│  执行命令         │  docker compose exec svc cmd  在容器中执行     │
│                  │  docker compose run --rm svc   一次性运行      │
│                  │                                               │
├──────────────────┼───────────────────────────────────────────────┤
│                  │                                               │
│  构建管理         │  docker compose build          构建镜像        │
│                  │  docker compose build --no-cache 不用缓存     │
│                  │  docker compose pull            拉取镜像       │
│                  │                                               │
├──────────────────┼───────────────────────────────────────────────┤
│                  │                                               │
│  扩缩容          │  docker compose up --scale svc=3  扩展实例     │
│                  │                                               │
├──────────────────┼───────────────────────────────────────────────┤
│                  │                                               │
│  配置与调试       │  docker compose config         查看最终配置    │
│                  │  docker compose -f a.yml -f b.yml up 多文件   │
│                  │  docker compose --profile x up  按 profile    │
│                  │                                               │
├──────────────────┼───────────────────────────────────────────────┤
│                  │                                               │
│  开发模式         │  docker compose watch          文件监听        │
│                  │  docker compose up --watch     启动+监听       │
│                  │                                               │
└──────────────────┴───────────────────────────────────────────────┘

4.6 docker-compose.yml 模板

通用 Web 应用模板(快速开始)

yaml
# 适用于大多数 Web 应用的通用模板
services:
  app:
    build: .
    ports:
      - "${APP_PORT:-8080}:8080"
    environment:
      DATABASE_URL: postgres://${DB_USER:-postgres}:${DB_PASSWORD:-postgres}@db:5432/${DB_NAME:-app}
      REDIS_URL: redis://redis:6379
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: ${DB_USER:-postgres}
      POSTGRES_PASSWORD: ${DB_PASSWORD:-postgres}
      POSTGRES_DB: ${DB_NAME:-app}
    volumes:
      - db-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  db-data:
  redis-data:

WordPress 部署模板

yaml
services:
  wordpress:
    image: wordpress:latest
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${WP_DB_PASSWORD:-wordpress}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wp-content:/var/www/html/wp-content
    depends_on:
      db:
        condition: service_healthy
    restart: unless-stopped

  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-rootpassword}
      MYSQL_DATABASE: wordpress
      MYSQL_USER: wordpress
      MYSQL_PASSWORD: ${WP_DB_PASSWORD:-wordpress}
    volumes:
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 30s
    restart: unless-stopped

volumes:
  wp-content:
  db-data:

ELK 日志栈模板

yaml
services:
  elasticsearch:
    image: elasticsearch:8.12.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"
    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:9200/_cluster/health || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 10
    restart: unless-stopped

  logstash:
    image: logstash:8.12.0
    volumes:
      - ./logstash/pipeline:/usr/share/logstash/pipeline:ro
    depends_on:
      elasticsearch:
        condition: service_healthy
    restart: unless-stopped

  kibana:
    image: kibana:8.12.0
    ports:
      - "5601:5601"
    environment:
      ELASTICSEARCH_HOSTS: http://elasticsearch:9200
    depends_on:
      elasticsearch:
        condition: service_healthy
    restart: unless-stopped

volumes:
  es-data:

📝 学习建议:Docker Compose 是多容器管理的核心工具,建议按以下顺序学习:

  1. 先完成 Demo 1 和 Demo 2,理解基本语法和工作流
  2. 深入完成 Demo 3(完整 Web 应用栈),这是最贴近实际项目的练习
  3. 学习多配置文件和 Profiles(Demo 4 和 Demo 5),为生产部署做准备
  4. 尝试将自己的项目用 Compose 容器化

掌握 Compose 后,你就能在本地一条命令搭建起完整的开发环境,大幅提升开发效率。