从零到一:Docker Compose 使用详解,告别手动启动多个容器的痛苦

目录

[① 导读卡片](#① 导读卡片)

[② 背景与目标](#② 背景与目标)

[为什么需要 Compose?](#为什么需要 Compose?)

学完你能做什么?

[③ 概念与原理](#③ 概念与原理)

[Compose 的本质是什么?](#Compose 的本质是什么?)

底层工作流程

[④ 逻辑与对比](#④ 逻辑与对比)

[用 vs 不用 Compose 的对比](#用 vs 不用 Compose 的对比)

[什么时候该用 Compose?](#什么时候该用 Compose?)

[⑤ 核心详解](#⑤ 核心详解)

[5.1 基础结构 ------ 一切从 docker-compose.yml 开始](#5.1 基础结构 —— 一切从 docker-compose.yml 开始)

[5.2 五大核心字段详解](#5.2 五大核心字段详解)

[① services ------ 容器定义](#① services —— 容器定义)

[② depends_on + healthcheck ------ 真正的依赖管理](#② depends_on + healthcheck —— 真正的依赖管理)

[③ volumes ------ 数据持久化三兄弟](#③ volumes —— 数据持久化三兄弟)

[④ environment / env_file ------ 配置管理](#④ environment / env_file —— 配置管理)

[⑤ networks ------ 容器通信](#⑤ networks —— 容器通信)

[5.3 常用命令一览](#5.3 常用命令一览)

[⑥ 案例实战](#⑥ 案例实战)

[实战一:本地开发一个 Node.js + Redis + MySQL 应用](#实战一:本地开发一个 Node.js + Redis + MySQL 应用)

[实战二:多环境配置分离(开发 / 生产)](#实战二:多环境配置分离(开发 / 生产))

[实战三:微服务本地联调(A 调用 B)](#实战三:微服务本地联调(A 调用 B))

[⑦ 避坑 & 最佳实践](#⑦ 避坑 & 最佳实践)

常见坑点

最佳实践清单

[⑧ 总结 & 路线图](#⑧ 总结 & 路线图)

本文要点速记

下一步学习路线

推荐的三个小实验(做完就是实战派)


① 导读卡片

一句话定位 :Docker Compose 是 Docker 官方容器编排工具,通过一个 YAML 文件定义和运行多容器应用,从此告别手敲无数条 docker run 命令。

  • 适合人群 :已掌握 docker run 基本操作,希望管理多容器应用的开发者

  • 难度:⭐⭐☆☆☆(入门进阶)

  • 阅读时长:15 分钟

  • 前置知识 :Docker 基础命令(docker rundocker networkdocker volume


② 背景与目标

为什么需要 Compose?

先看一个真实场景:你要跑一个 Nginx + Tomcat 集群,传统方式需要手动敲:

复制代码
docker network create mynet
docker run -d --name tomcat1 --network mynet tomcat
docker run -d --name tomcat2 --network mynet tomcat
docker run -d --name nginx -p 80:80 --network mynet -v ./nginx.conf:/etc/nginx/nginx.conf nginx

docker network create mynet

docker run -d --name tomcat1 --network mynet tomcat

docker run -d --name tomcat2 --network mynet tomcat

docker run -d --name nginx -p 80:80 --network mynet -v

./nginx.conf:/etc/nginx/nginx.conf nginx

这条命令链暴露了四大痛点:

痛点 后果
命令长、参数多 容易写错,排查半天才发现少了个参数
缺乏一键操作 启动/停止/重启每个容器都得单独操作
依赖顺序靠猜 Nginx 可能在 Tomcat 之前启动,日志乱飘但无法保证顺序
跨环境难复现 换台机器,同一套命令又得重新敲一遍

学完你能做什么?

  • ✅ 用一个 YAML 文件定义整个应用栈(Web + 数据库 + 缓存 + Nginx)

  • ✅ 一行 docker compose up 启动全部服务

  • ✅ 实现开发/生产多环境配置分离

  • ✅ 掌握微服务本地联调的正确姿势

  • ✅ 熟练使用 Compose 生命周期管理命令


③ 概念与原理

Compose 的本质是什么?

Compose 是一个容器编排工具,它的核心工作流极其简单:

复制代码
你写 YAML 描述需求 → Compose 解析 YAML → 调用 Docker API 创建/管理资源

Compose 引入了一个重要概念------项目(Project) 。每个 Compose 文件对应一个项目,项目内的资源(网络、卷、容器)以 项目名_资源名 的方式隔离,不同项目之间互不干扰。

💡 核心认知 :你理解了 docker run 的各个参数,就理解了 90% 的 Compose 配置项。Compose 不会魔法,它只是把命令行参数变成了结构化 YAML。

底层工作流程

当执行 docker compose up 时,Compose 内部做了这些事情:

  1. 解析 YAML → 读取 docker-compose.yml,合并 override 文件,替换环境变量

  2. 创建网络 → 为项目创建默认网络(项目名_default),所有服务加入此网络

  3. 创建卷 → 检查命名卷是否存在,不存在则创建

  4. 构建/拉取镜像 → 根据 buildimage 字段构建或拉取

  5. 按依赖顺序启动容器 → 根据 depends_on 确定启动顺序

  6. 健康检查 → 如果配置了 healthcheck,等待服务就绪后才启动依赖它的服务


④ 逻辑与对比

用 vs 不用 Compose 的对比

维度 手写 docker run Docker Compose
定义方式 命令行参数 声明式 YAML
启动全部服务 写脚本或一个个敲 docker compose up
停止全部 docker stop c1 c2 c3... docker compose down
依赖管理 手动保证顺序 depends_on 自动处理
环境复现 需手动记录所有参数 一个 YAML 文件搞定
多环境支持 改参数或脚本 多文件组合 + 变量替换

什么时候该用 Compose?

场景 建议
本地开发一个 Web + 数据库的应用 强烈推荐
多服务微服务联调 必须用
生产环境单机部署 ✅ 可以,但 deploy 字段仅 Swarm 生效
生产环境多机集群 ❌ 改用 Kubernetes 或 Docker Swarm
只跑一个 Nginx 代理静态文件 ⚠️ 用 docker run 就够了,不必杀鸡用牛刀

⑤ 核心详解

5.1 基础结构 ------ 一切从 docker-compose.yml 开始

一个标准 Compose 文件的结构:

XML 复制代码
version: '3.8'           # 版本号,3.8 是目前最稳定的版本
​
services:                # 定义所有服务(容器)
  service_name:
    image: nginx:alpine  # 使用的镜像
    build: ./path        # 或通过 Dockerfile 构建
    ports:
      - "80:80"          # 端口映射
    volumes:
      - ./data:/app      # 卷挂载
    environment:         # 环境变量
      - KEY=VALUE
    depends_on:          # 依赖关系
      - other_service
    networks:            # 网络
      - mynet
​
networks:                # 定义网络
  mynet:
​
volumes:                 # 定义命名卷
  mydata:

5.2 五大核心字段详解

services ------ 容器定义

每个 service 对应一个或多个容器。核心写法分为两类:

用已有镜像启动:

XML 复制代码
services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"

从 Dockerfile 构建:

XML 复制代码
services:
  backend:
    build: ./backend          # 指定 Dockerfile 目录
    # build:
    #   context: ./backend    # 构建上下文
    #   dockerfile: Dockerfile.prod  # 指定 Dockerfile
    image: myapp:latest       # 构建后镜像名

buildimage 同时存在时,Compose 会先构建镜像,并将其命名为 项目名_服务名

depends_on + healthcheck ------ 真正的依赖管理

depends_on 只能保证启动顺序,不能保证服务就绪

错误用法 ------ 以为启动了就等于可用了:

XML 复制代码
services:
  backend:
    depends_on:
      - db        # db 容器启动了,但 PostgreSQL 可能还在初始化

正确用法 ------ 配合 healthcheck:

XML 复制代码
services:
  db:
    image: postgres:14
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s    # 每 10 秒检查一次
      timeout: 5s      # 单次检查超时
      retries: 5       # 连续失败 5 次算不健康
​
  backend:
    depends_on:
      db:
        condition: service_healthy  # 等 db 健康后才启动
volumes ------ 数据持久化三兄弟
XML 复制代码
services:
  app:
    volumes:
      - db_data:/var/lib/postgresql/data   # 命名卷(Docker 管理)
      - ./src:/app                          # 绑定挂载(热加载)
      - /app/node_modules                   # 匿名卷(防止覆盖)

三种卷的区别:

类型 写法特征 宿主位置 使用场景
命名卷 名字开头(无/ /var/lib/docker/volumes/卷名/_data/ 数据库数据、持久化核心数据
绑定挂载 /./开头 你指定的路径 开发时代码热加载、配置文件注入
匿名卷 只有容器路径 Docker 随机生成 屏蔽容器内目录不被宿主机覆盖
environment / env_file ------ 配置管理
XML 复制代码
services:
  app:
    # 方式一:直接写(适合测试、非敏感)
    environment:
      - DB_HOST=db
      - DB_PORT=5432
​
    # 方式二:从 .env 文件引用变量
    environment:
      - DB_PASSWORD=${DB_PASSWORD}
​
    # 方式三:从外部文件加载(推荐敏感信息)
    env_file:
      - ./config/db.env
      - ./config/app.env
.env 文件(与 docker-compose.yml 同级):

DB_PASSWORD=MyStrongPass123
DB_USER=admin
networks ------ 容器通信
XML 复制代码
services:
  web:
    networks:
      - frontend
      - backend
​
  api:
    networks:
      - backend
​
  db:
    networks:
      - backend
​
networks:
  frontend:
  backend:

关键知识点:

  • 默认情况下,Compose 为每个项目创建名为 项目名_default 的网络

  • 同一网络内的服务可以通过 服务名 互相通信(内置 DNS 解析)

  • extra_hosts 可以添加自定义 hosts 映射(如访问宿主机)

5.3 常用命令一览

需求 命令
启动所有服务(前台) docker compose up
启动所有服务(后台) docker compose up -d
停止并删除容器、网络 docker compose down
停止并删除一切(含卷) docker compose down -v
查看运行中的服务 docker compose ps
查看日志(实时) docker compose logs -f
查看某个服务日志 docker compose logs -f backend
进入容器调试 docker compose exec backend bash
重建某个服务 docker compose up -d --build backend
查看最终配置 docker compose config

docker compose config 这个命令非常实用------它会输出变量替换后的最终 YAML,排错利器。


⑥ 案例实战

实战一:本地开发一个 Node.js + Redis + MySQL 应用

需求:代码修改自动重启,数据持久化,日志实时查看。

项目结构:

复制代码
project/
├── docker-compose.yml
├── .env
├── backend/
│   ├── Dockerfile
│   └── app.js

docker-compose.yml

XML 复制代码
version: '3.8'
​
services:
  mysql:
    image: mysql:8.0
    env_file:
      - ./config/mysql.env
    volumes:
      - mysql_data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - "3307:3306"    # 避免本地已有 MySQL 冲突
​
  redis:
    image: redis:7-alpine
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    ports:
      - "6379:6379"
​
  backend:
    build: ./backend
    ports:
      - "3000:3000"
    volumes:
      - ./backend:/app          # 代码热加载
      - /app/node_modules       # 不覆盖容器内的 node_modules
    environment:
      - NODE_ENV=development
      - DB_HOST=mysql
      - REDIS_HOST=redis
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
​
volumes:
  mysql_data:
  redis_data:
.env:

MYSQL_ROOT_PASSWORD=devpass123
MYSQL_DATABASE=myapp

实战二:多环境配置分离(开发 / 生产)

核心思路:基础配置 + 环境覆盖文件。

docker-compose.yml(基础配置):

XML 复制代码
version: '3.8'
services:
  app:
    build: ./app
    ports:
      - "8080:8080"
    environment:
      - DB_HOST=db
    depends_on:
      - db
​
  db:
    image: postgres:14
    volumes:
      - pg_data:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
​
volumes:
  pg_data:

docker-compose.dev.yml(开发覆盖):

XML 复制代码
services:
  app:
    volumes:
      - ./app:/app            # 开发时挂载代码
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - DEBUG=true
    ports:
      - "9229:9229"           # 调试端口
​
  db:
    ports:
      - "5432:5432"           # 暴露数据库端口方便客户端连接

docker-compose.prod.yml(生产覆盖):

XML 复制代码
services:
  app:
    # 生产环境去掉代码挂载,使用 build 镜像
    environment:
      - NODE_ENV=production
    restart: always
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
​
  db:
    restart: always
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

使用方式:

XML 复制代码
# 开发环境
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d
​
# 生产环境
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

实战三:微服务本地联调(A 调用 B)

场景:两个服务互相调用,都需要访问数据库。

关键知识点验证

Q:两个服务不加 links 能互相访问吗? A:。Compose 默认网络已提供 DNS 解析,服务名就是主机名。

XML 复制代码
version: '3.8'
​
services:
  service-a:
    image: myapp/service-a
    environment:
      - SERVICE_B_URL=http://service-b:8081    # 直接用服务名
​
  service-b:
    image: myapp/service-b
    ports:
      - "8081:8081"

Q:容器怎么访问宿主机服务(比如本地跑的数据库)? A:使用 extra_hosts

XML 复制代码
services:
  app:
    extra_hosts:
      - "host.docker.internal:host-gateway"   # Docker 20.10+ 自动解析

Mac/Windows 可直接用 host.docker.internal;Linux 用 172.17.0.1(Docker 网关 IP)。


⑦ 避坑 & 最佳实践

常见坑点

坑 1:depends_on 不等于服务就绪

复制代码
# ❌ 错误:以为 depends_on 后数据库就能连上
depends_on:
  - db
​
# ✅ 正确:配 healthcheck + condition
depends_on:
  db:
    condition: service_healthy

坑 2:宿主机目录覆盖了容器内的依赖

复制代码
# ❌ 错误:宿主机的空目录覆盖了整个 /app
volumes:
  - ./backend:/app    # 如果 ./backend 没有 node_modules...
​
# ✅ 正确:用匿名卷"保护"容器目录
volumes:
  - ./backend:/app
  - /app/node_modules   # 匿名卷,确保容器内的 node_modules 不被覆盖

坑 3:deploy 字段在单机 Compose 中被忽略

复制代码
# ❌ 错误:单机运行,deploy.replicas 不生效
services:
  app:
    deploy:
      replicas: 3
​
# ✅ 替代:使用 --scale
docker compose up -d --scale app=3

坑 4:忘记声明命名卷导致难以管理

复制代码
# ❌ 不声明顶级 volumes,Compose 也工作,但不好管理
services:
  db:
    volumes:
      - db_data:/var/lib/postgresql/data
​
# ✅ 显式声明顶级 volumes 是好习惯
volumes:
  db_data:

最佳实践清单

  1. 总是显式声明顶级 volumesnetworks --- 即使可以隐式创建,显式声明更方便管理

  2. 敏感信息走 env_file --- 密码写在 .env 或环境中,.gitignore 排除

  3. 开发环境用多文件组合 --- 基础配置 + 覆盖文件,避免一个文件改来改去

  4. 健康检查是必须的 --- 没有 healthcheck 的 depends_on 约等于没用

  5. docker compose config 排错 --- 变量替换后是否符合预期,一跑就知道

  6. 为项目命名 --- 用 --project-namename: 字段显式指定,避免目录名影响

  7. 限制日志大小 --- 默认日志无限增长会撑爆磁盘

  8. docker compose down -v 慎用 --- -v 会删除命名卷,数据全没了


⑧ 总结 & 路线图

本文要点速记

模块 关键结论
核心原理 Compose = 把 docker run 参数写成 YAML + 自动编排
五大字段 services networks volumes depends_on environment
依赖管理 depends_on 只保证启动顺序,healthcheck 保证服务就绪
卷的类型 命名卷(Docker 管)、绑定挂载(用户管)、匿名卷(屏蔽覆盖)
多环境 基础文件 + 覆盖文件,变量替换 + .env

下一步学习路线

复制代码
第 1 周:掌握 Compose 五大核心字段
        用 Compose 跑 WordPress(MySQL + WordPress)
        ↓
第 2 周:学会开发/生产分离模式
        写 dev.yml 和 prod.yml,理解覆盖机制
        ↓
第 3 周:真实项目迁移
        把个人项目从 docker run 改写成 Compose
        要求:热加载 + 日志持久化 + 一键启动
        ↓
第 4 周:深入最佳实践
        阅读开源项目(GitLab、Nextcloud)的 docker-compose.yml
        学习 secrets 管理、日志驱动、网络隔离等

推荐的三个小实验(做完就是实战派)

  1. 加健康检查 :在你之前的 Nginx + Tomcat Compose 文件中,给 Tomcat 添加 healthcheck,让 Nginx 只在 Tomcat 就绪后才启动

  2. 水平扩展docker compose up -d --scale tomcat=3,观察 Nginx 的 upstream 变化

  3. 日志持久化 :将 Tomcat 的日志目录通过卷挂载到本地 ./logs

Compose 不复杂,它就是把你会的手动操作自动化了。每学一个字段,想想"如果不写 Compose,我该怎么用 docker run 做到",理解了这层对应关系,你就真正掌握了。