切换语言
切换主题

Docker Compose 多服务编排:本地开发环境一键启动

入职第一天的下午,我盯着屏幕上第 5 个报错弹窗——MySQL 端口被占用,Redis 版本不兼容,RabbitMQ 死活连不上。旁边的前辈看了眼我的屏幕,叹了口气:“你这是装了多久?”

“从上午 9 点到现在。” 我小声说。

4 个小时。装 3 个数据库,花了 4 个小时。而这还只是开始,后面还有 ElasticSearch 和 MongoDB 要装。

那是我第一次意识到:本地开发环境,真的是个坑。一个巨大的、看不见底的坑。

后来我们团队换了 Docker Compose。新人 clone 仓库,docker-compose up -d,5 分钟,全部服务启动完毕。MySQL、Redis、RabbitMQ、API、Web,一行命令,干干净净。项目切换?换个目录,启动另一个 compose 文件就行。清理?docker-compose down -v,连数据卷一起删掉,不留痕迹。

这篇文章,我想分享的就是这个转变:如何用 Docker Compose 编排多服务,让本地开发环境从”噩梦”变成”一行命令的事”。

为什么需要多服务编排

说实话,十年前做单体应用的时候,环境配置简单得很——装个 JDK,配个数据库连接字符串,项目就能跑。但现在不一样了。

大多数项目拆成了微服务架构,或者至少也是前后端分离。一个典型的本地开发环境,至少需要这些东西:前端 Web 服务、后端 API 服务、MySQL 存业务数据、Redis 做缓存和 Session、RabbitMQ 处理异步消息。有的项目还要加 ElasticSearch 做搜索,MongoDB 存日志。

这时候问题就来了。

手动安装的痛苦

每台机器都要装一遍。MySQL 要选版本——5.7 还是 8.0?装错了版本,SQL 语法不兼容。Redis 要配端口——默认 6379,但万一被别的服务占用了呢?RabbitMQ 还要装 Erlang 运行环境……光 RabbitMQ 的安装教程,我就看了半小时。

装完之后,还有版本冲突的问题。本地跑过别的项目,MySQL 还留着旧版本的数据。端口冲突,服务启动失败。排查半天,发现是某个僵尸进程没杀干净。

最崩溃的是项目切换。刚做完项目 A,要切换到项目 B。项目 A 的 MySQL 占着 3306,项目 B 也想用 3306。要么改配置文件,要么停掉项目 A 的服务。改完配置,过两天又切回项目 A,配置又要改回来。

来回折腾。

团队协作的噩梦

“在我电脑上能跑啊。”

这句话,每个团队都听过吧。新人入职,clone 代码,装环境,跑不起来。为什么?老项目的 MySQL 版本是 5.7,新人装了 8.0;老项目的 Redis 没设密码,新人本地的 Redis 设了密码。配置文件改了好几处,还是跑不起来。

最后前辈过来帮忙,两个人花了大半天,才把环境调通。

这一天的时间,就这么没了。

Compose 的解决思路

Docker Compose 的思路很简单:把所有服务打包成容器,用一份配置文件统一管理。

你不用关心每个数据库怎么安装、怎么配置版本、怎么分配端口。Compose 文件里写清楚,一键启动,所有服务按配置运行。项目切换?换个目录,启动另一份 Compose 文件。清理?一行命令删掉所有容器和卷。

这就像从”自己组装电脑”变成了”买整机”。你不用管内存条怎么插、显卡怎么装电源线——厂商已经帮你弄好了,开机就能用。

docker-compose.yml 核心配置

先看一份完整的配置文件。假设我们的项目有 4 个服务:Web 前端、API 后端、MySQL 数据库、Redis 缓存。

# docker-compose.yml
version: "3.8"  # Compose 文件版本,3.8 支持大多数配置选项

services:
  # 前端 Web 服务
  web:
    build: ./frontend  # 从本地 frontend 目录构建镜像
    ports:
      - "3000:3000"  # 宿主机 3000 -> 容器 3000
    depends_on:
      - api  # 依赖 api 服务,api 先启动
    environment:
      - API_URL=http://api:8080  # 前端访问后端的地址

  # 后端 API 服务
  api:
    build: ./backend  # 从本地 backend 目录构建镜像
    ports:
      - "8080:8080"
    depends_on:
      - mysql
      - redis  # 依赖数据库和缓存
    environment:
      - DB_HOST=mysql  # 数据库地址(容器名)
      - DB_PORT=3306
      - DB_USER=root
      - DB_PASSWORD=dev123  # 开发环境密码,生产环境用 .env 文件
      - REDIS_HOST=redis
      - REDIS_PORT=6379

  # MySQL 数据库
  mysql:
    image: mysql:8.0  # 直接使用官方镜像,不构建
    ports:
      - "3306:3306"
    environment:
      - MYSQL_ROOT_PASSWORD=dev123
      - MYSQL_DATABASE=myapp  # 自动创建数据库
    volumes:
      - mysql_data:/var/lib/mysql  # 数据持久化到卷

  # Redis 缓存
  redis:
    image: redis:7-alpine  # alpine 版本更小
    ports:
      - "6379:6379"

volumes:
  mysql_data:  # 定义数据卷,MySQL 数据持久化存储

核心字段解释

services 下面定义所有服务。每个服务可以有三种来源:build 从本地代码构建,image 直接拉取官方镜像,或者两者结合(本地构建 + 基于某个镜像)。

ports 做端口映射。格式是 "宿主机端口:容器端口"。上面的配置里,Web 映射到 3000,API 映射到 8080,MySQL 映射到 3306,Redis 映射到 6379。如果本地端口被占用,可以改宿主机端口,比如 "13006:3306",这样你用 localhost:13006 连接 MySQL。

depends_on 控制启动顺序。MySQL 和 Redis 先启动,然后 API(依赖它们),最后 Web(依赖 API)。但有个坑,下面会讲。

environment 设置环境变量。数据库密码、连接地址、端口号都可以在这里配置。注意,生产环境不要把密码写在这里,用 .env 文件或者环境变量注入。

volumes 做数据持久化。MySQL 的数据存到 mysql_data 卷,这样容器删掉后数据不会丢失。下次启动,数据还在。

一个容易踩的坑

容器名就是服务名。上面配置里,API 服务连接数据库用的是 DB_HOST=mysql,不是 localhost。为什么?

因为每个容器都是独立的网络环境。localhost 在 API 容器里指向的是 API 容器自己,不是宿主机,也不是 MySQL 容器。Compose 自动创建一个内部网络,服务之间用服务名互相访问。mysql 这个名字,就是 MySQL 容器在网络里的地址。

第一次写 Compose 文件的时候,我就踩了这个坑——写了 localhost:3306,死活连不上数据库。后来才发现,要用容器名。

服务依赖与启动顺序

depends_on 看起来很简单:MySQL 先启动,API 再启动。但实际上有个微妙的问题。

Compose 的 depends_on 只保证容器启动顺序,不保证服务就绪顺序。换句话说,MySQL 容器启动了,但 MySQL 服务可能还没准备好接受连接——正在初始化数据库、加载配置、启动监听端口。API 服务这时候去连接,大概率会失败。

我就遇到过这种情况。docker-compose up 之后,API 立即报错:数据库连接失败。过了 10 秒再试,又成功了。原因是 MySQL 容器启动了,但 MySQL 服务还没准备好。

解决方案一:健康检查

Compose 支持在配置里加健康检查。只有健康检查通过后,依赖它的服务才会启动。

services:
  mysql:
    image: mysql:8.0
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s  # 每 5 秒检查一次
      timeout: 3s   # 超时时间
      retries: 10   # 失败 10 次才认为 unhealthy
    # ... 其他配置

  api:
    depends_on:
      mysql:
        condition: service_healthy  # 等待 MySQL 健康检查通过

MySQL 会执行 mysqladmin ping 命令,检查自己是否可以接受连接。5 秒检查一次,最多等 10 次(50 秒)。只有健康检查通过了,API 才启动。

这个方法有效,但有个缺点:每个服务都要写健康检查配置。有些官方镜像(比如 Redis)没有提供方便的健康检查命令,要自己想办法。

解决方案二:应用层重试

更省事的办法是在应用代码里加重试逻辑。连接失败,等几秒再试。MySQL 启动慢,等它准备好就行。

指数退避是常用的策略:第一次等 1 秒,第二次等 2 秒,第三次等 4 秒……逐渐增加等待时间。大多数数据库在 30 秒内都能准备好。

Node.js 的话,可以用 mysql2 的连接池配置:

const pool = mysql.createPool({
  host: 'mysql',
  port: 3306,
  user: 'root',
  password: 'dev123',
  database: 'myapp',
  waitForConnections: true,  // 等待连接可用
  connectionLimit: 10,
  queueLimit: 0,
});

Python 的话,用 tenacity 库做重试:

from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(5), wait=wait_exponential(multiplier=1, min=2, max=10))
def connect_db():
    return mysql.connector.connect(host='mysql', ...)

两种方案的选择

健康检查更精确——只有数据库真的准备好了,API 才启动。但配置稍麻烦,每个数据库都要写对应的检查命令。

应用层重试更简单——代码里加几行,不用改 Compose 配置。缺点是 API 启动后会有一段时间不断重试,日志里会有连接失败的报错(虽然不影响最终结果)。

我个人更喜欢应用层重试,因为更省事,而且大多数情况下都能正常工作。健康检查作为备选,用在启动特别慢的服务上。

多环境配置策略

本地开发、测试环境、生产环境,配置通常不一样。比如端口映射——开发环境需要把数据库端口暴露出来,方便本地调试;生产环境不需要,数据库只在容器内部网络访问。

如果把这些配置都写在一份文件里,切换环境就要改文件,改完又改回来。麻烦,还容易改错。

Compose 有个设计解决这个问题:基础文件 + 覆盖文件。

基础文件:公共配置

docker-compose.yml 写所有环境共享的配置——服务定义、镜像版本、内部网络、数据卷。

# docker-compose.yml(基础配置)
version: "3.8"

services:
  web:
    build: ./frontend
    # ports 不写,由覆盖文件补充

  api:
    build: ./backend
    environment:
      - DB_HOST=mysql
      - REDIS_HOST=redis
    # ports 不写

  mysql:
    image: mysql:8.0
    volumes:
      - mysql_data:/var/lib/mysql
    # ports 不写,生产环境不需要暴露

  redis:
    image: redis:7-alpine

volumes:
  mysql_data:

端口映射没写,因为不同环境的端口配置不一样。

开发环境覆盖文件

docker-compose.override.yml 补充开发环境的特定配置——端口映射、开发用的密码、调试用的环境变量。

# docker-compose.override.yml(开发环境)
version: "3.8"

services:
  web:
    ports:
      - "3000:3000"  # 开发环境暴露端口,方便本地访问

  api:
    ports:
      - "8080:8080"
    environment:
      - DEBUG=true  # 开发环境开启调试模式

  mysql:
    ports:
      - "3306:3306"  # 开发环境暴露数据库端口,方便连接调试
    environment:
      - MYSQL_ROOT_PASSWORD=dev123  # 开发环境简单密码

  redis:
    ports:
      - "6379:6379"

Compose 有个默认行为:执行 docker-compose up 时,自动合并 docker-compose.ymldocker-compose.override.yml。两份文件的配置叠加,override 的配置覆盖基础配置。

所以本地开发,直接 docker-compose up,就能拿到开发环境的完整配置。

生产环境覆盖文件

docker-compose.prod.yml 补充生产环境的配置——不暴露端口、使用生产密码、连接外部服务。

# docker-compose.prod.yml(生产环境)
version: "3.8"

services:
  web:
    # 不暴露端口,由反向代理(nginx)访问

  api:
    environment:
      - DB_HOST={{DB_HOST}}  # 从环境变量读取,不在文件里写密码
      - DB_PASSWORD={{DB_PASSWORD}}

  mysql:
    # 不暴露端口,外部无法直接连接
    environment:
      - MYSQL_ROOT_PASSWORD={{MYSQL_ROOT_PASSWORD}}

生产环境启动时,用 -f 参数指定配置文件:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

两份文件叠加,prod 的配置覆盖基础配置。数据库端口不暴露,密码从环境变量读取。

环境变量注入

生产环境的密码不应该写在文件里。Compose 支持从 .env 文件或系统环境变量读取值。

# .env 文件(不要提交到 git)
DB_HOST=prod-mysql.internal
DB_PASSWORD=super_secret_password_123
MYSQL_ROOT_PASSWORD=another_secret

配置文件里用 {{VAR:-default}} 语法引用:

environment:
  - DB_HOST={{DB_HOST:-localhost}}  # 如果 DB_HOST 未设置,用 localhost
  - DB_PASSWORD={{DB_PASSWORD:-dev123}}

.env 文件不要提交到版本控制,用 .env.example 写示例值,团队成员复制后填自己的实际配置。

一键命令实战

配置写好了,接下来就是启动、停止、调试。掌握这几个命令,日常操作基本够用。

启动全部服务

docker-compose up -d

up 启动所有服务。-d 表示后台运行(detached mode),不会占用终端。不加 -d,所有服务的日志会直接打印到终端,Ctrl+C 可以停止。

启动后,Compose 会拉取镜像(如果用 image)、构建镜像(如果用 build)、创建容器、启动服务。第一次启动会比较慢,因为要拉取镜像。后续启动就快了,镜像已经存在。

查看服务状态

docker-compose ps

列出所有容器的运行状态。输出类似这样:

NAME                COMMAND             SERVICE   STATUS    PORTS
myapp-web-1         "npm start"         web       running   0.0.0.0:3000->3000/tcp
myapp-api-1         "node index.js"     api       running   0.0.0.0:8080->8080/tcp
myapp-mysql-1       "mysqld"            mysql     running   0.0.0.0:3306->3306/tcp
myapp-redis-1       "redis-server"      redis     running   0.0.0.0:6379->6379/tcp

STATUS 显示 running 表示正常运行。如果显示 exited 或 error,说明服务启动失败。

查看服务日志

docker-compose logs -f api

查看 API 服务的日志。-f 表示持续跟踪(follow),新日志会实时显示。不加 -f 只显示已有日志。

不加服务名,docker-compose logs -f 会显示所有服务的日志,但日志量大的时候会很乱。

停止并清理

docker-compose down

停止所有容器,删除容器和网络。但数据卷不会删除——MySQL 的数据还在。

如果要彻底清理,包括数据卷:

docker-compose down -v

-v 删除数据卷。下次启动,MySQL 会重新初始化,数据全部清空。调试的时候经常用这个命令——数据乱了,清掉重来。

重新构建镜像

代码改了,要重新构建镜像:

docker-compose build api

只构建 API 服务的镜像。构建完,要重启容器:

docker-compose up -d api

或者一步到位,构建并重启:

docker-compose up -d --build api

--build 强制重新构建镜像,即使镜像已存在。

常用命令速查表

命令作用
docker-compose up -d后台启动全部服务
docker-compose ps查看运行状态
docker-compose logs -f api查看 API 日志
docker-compose down停止并删除容器
docker-compose down -v停止并删除容器和数据卷
docker-compose restart api重启 API 服务
docker-compose build api重新构建 API 镜像

这些命令覆盖了日常 90% 的操作。其他命令(execcptop)用到的时候再查文档。

Docker Compose 多服务编排实战

用 Docker Compose 编排 Web、API、MySQL、Redis 四个服务,实现本地开发环境一键启动

⏱️ 预计耗时: 15 分钟

  1. 1

    步骤1: 创建 docker-compose.yml 文件

    在项目根目录创建配置文件:

    ```yaml
    version: "3.8"
    services:
    web:
    build: ./frontend
    ports: ["3000:3000"]
    depends_on: [api]
    api:
    build: ./backend
    ports: ["8080:8080"]
    depends_on: [mysql, redis]
    mysql:
    image: mysql:8.0
    environment:
    MYSQL_ROOT_PASSWORD: dev123
    MYSQL_DATABASE: myapp
    redis:
    image: redis:7-alpine
    ```

    注意:容器间通信用服务名(如 DB_HOST=mysql),不是 localhost
  2. 2

    步骤2: 启动全部服务

    在 docker-compose.yml 所在目录执行:

    ```bash
    docker-compose up -d
    ```

    • 首次启动会拉取镜像,耗时较长
    • 后续启动直接使用已有镜像,秒级完成
    • 不加 -d 会占用终端显示日志
  3. 3

    步骤3: 验证服务运行状态

    检查所有容器是否正常启动:

    ```bash
    docker-compose ps
    ```

    • STATUS 显示 running 表示正常
    • 如有 exited 或 error,用 logs 排查:
    ```bash
    docker-compose logs api
    ```
  4. 4

    步骤4: 配置多环境(可选)

    创建 docker-compose.override.yml(开发环境):

    ```yaml
    version: "3.8"
    services:
    mysql:
    ports: ["3306:3306"]
    ```

    • Compose 自动合并 override 文件
    • 生产环境用 -f 指定:
    ```bash
    docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
    ```
  5. 5

    步骤5: 清理环境

    停止并删除所有容器:

    ```bash
    docker-compose down # 保留数据卷
    docker-compose down -v # 删除数据卷(数据清空)
    ```

    • 切换项目时用 down 清理
    • 数据乱了想重来,用 down -v

结尾

对比一下传统方式和 Compose 方式的效率:

操作传统方式Compose 方式
新人入职装环境4-8 小时5 分钟(clone + up)
项目切换改配置、停服务、重启切目录、启动另一份 compose
清理环境手动卸载、排查残留进程一行命令删容器和卷
团队环境一致性每台机器都可能不一样配置文件统一,环境完全一致

差距很明显。

如果你还在手动装数据库、改配置文件、排查端口冲突,试试 Docker Compose。先从简单的项目开始——一个 API + 一个 MySQL。写一份 docker-compose.yml,跑起来。熟练了,再加 Redis、RabbitMQ,做多环境配置。

团队里推广的话,把 docker-compose.ymldocker-compose.override.yml 提交到仓库,加个 README 说明启动步骤。新人入职,clone 代码,一行命令,开发环境就准备好了。

这比写”环境配置文档”靠谱多了。文档会过期,配置文件不会。

常见问题

docker-compose.yml 和 Dockerfile 有什么区别?
Dockerfile 定义单个镜像的构建步骤,docker-compose.yml 定义多个容器如何协同工作。简单说:Dockerfile 造镜像,Compose 管容器。一个项目通常有一个 Dockerfile(或多个,每个服务一个),和一个 docker-compose.yml。
depends_on 能保证服务就绪吗?
不能。depends_on 只保证容器启动顺序,不保证服务就绪。MySQL 容器启动了,但服务可能还在初始化。解决方案有两种:一是在 docker-compose.yml 里加 healthcheck,二是应用代码里加重试逻辑。推荐应用层重试,更简单可靠。
容器内怎么访问宿主机的服务?
用 host.docker.internal(Docker Desktop)或宿主机 IP(Linux)。比如宿主机跑了个 Redis,容器内连接 host.docker.internal:6379。注意:Linux 上 host.docker.internal 需要额外配置,更简单的方式是用 --network host 模式(但会失去网络隔离)。
数据存在哪里?容器删了数据会丢吗?
volumes 配置的数据存在 Docker 管理的数据卷里,容器删了数据不会丢。docker-compose down 只删容器和网络,不删卷。要清空数据,用 docker-compose down -v。开发环境调试时经常用这个命令:数据乱了,清掉重来。
多环境配置(dev/test/prod)怎么管理?
用基础文件 + 覆盖文件策略。docker-compose.yml 写公共配置,docker-compose.override.yml 写开发环境配置(Compose 默认自动加载),docker-compose.prod.yml 写生产环境配置。启动时用 -f 参数指定:docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d。
端口被占用怎么办?
改宿主机端口映射。比如 MySQL 默认映射 3306:3306,本地 3306 被占用,改成 13006:3306,用 localhost:13006 连接。或者直接不映射端口(去掉 ports 配置),只在容器内部网络访问,更安全。

13 分钟阅读 · 发布于: 2026年4月9日 · 修改于: 2026年4月9日

评论

使用 GitHub 账号登录后即可评论

相关文章