1. 项目概述与核心价值每次新项目启动或者老项目需要回归测试你是不是也经历过这样的场景开发、测试、产品经理每个人都在催你“本地环境怎么又挂了”“这个接口数据不对啊你那边能复现吗”“我本地数据库版本和你不一样跑出来的结果差好多。” 然后你就得花上半天甚至一天的时间去折腾本地的MySQL、Redis还有各种依赖的服务。版本冲突、端口占用、配置文件不一致这些问题反复出现严重拖慢了团队的交付节奏。这个项目要解决的就是这个让无数开发者头疼的“测试环境搭建”难题。它的核心目标很简单利用 Docker Compose实现一个包含 MySQL、Redis 和一个 Mock Server 的测试环境的一键式搭建与销毁。你不再需要手动安装、配置、启动这些服务也不用担心它们会污染你的宿主机环境。只需要一个命令docker-compose up -d一个干净、隔离、标准化的测试环境就准备就绪了。用完了一个docker-compose down所有资源清理得干干净净不留任何痕迹。这不仅仅是“偷懒”更是一种工程实践上的提效和规范。它确保了团队内所有成员无论是在 macOS、Windows 还是 Linux 上使用的都是完全一致的服务版本和配置从根本上杜绝了“在我机器上是好的”这类问题。对于后端开发、测试工程师甚至是需要本地联调的前端同学这都是一套极具价值的“开箱即用”的基础设施。2. 环境整体设计与思路拆解2.1 为什么选择 Docker Compose在决定技术方案时我们考虑过几种方式手动安装、虚拟机、纯 Docker 容器以及 Docker Compose。手动安装的弊端显而易见前面已经说过了。虚拟机虽然能实现环境隔离但资源消耗大启动慢镜像文件也巨大不适合频繁创建销毁。纯 Docker 容器需要你记住一长串的docker run命令和参数管理多个容器间的网络和依赖关系非常麻烦。Docker Compose 的优势就在这里凸显出来。它通过一个声明式的 YAML 配置文件docker-compose.yml定义了整个应用栈Stack所需要的所有服务Service、网络Network和数据卷Volume。你只需要描述“我想要什么”而不用关心“我怎么一步步做到”。它完美契合了我们“一键搭建”的需求单配置文件、服务编排、依赖管理、网络隔离。具体来说我们的docker-compose.yml文件会定义三个服务mysql、redis、mock-server。Compose 会为这三个服务自动创建一个独立的桥接网络让它们可以通过服务名互相访问同时与宿主机隔离。数据持久化的问题则通过挂载数据卷Volume到容器内的特定目录来解决这样即使容器销毁测试数据也不会丢失。2.2 核心组件选型与版本考量选型不是随便选最新版而是要兼顾稳定性、兼容性和团队的技术栈。MySQL: 我们选择MySQL 8.0。相比 5.78.0 在性能尤其是读性能、安全性和功能上都有显著提升例如默认的身份认证插件改为caching_sha2_password支持窗口函数、公共表表达式等。对于大多数2020年后的新项目8.0 已经是事实标准。如果团队老项目强依赖 5.7只需在配置文件中将镜像标签改为mysql:5.7即可其他配置基本通用。Redis: 我们选择Redis 7.2或最新的稳定版。Redis 6.0 之后引入了多线程 I/O处理网络请求性能提升明显同时提供了 ACL访问控制列表等新特性。对于测试环境我们可能用不到所有高级功能但使用新版本可以确保我们的代码和配置面向未来。同样如果需要降级修改镜像标签即可。Mock Server: 这是灵活性最高的部分。我们选择mockserver/mockserver这个官方镜像。MockServer 是一个功能非常强大的工具它不仅可以像普通 HTTP Server 一样返回预设的 JSON 数据还能根据请求内容路径、参数、Body动态匹配并返回不同的响应支持延迟、回调、验证请求是否发生等高级功能非常适合模拟复杂的第三方接口或未开发完成的内部接口。相比一些简单的 Node.js 或 Python 写的 Mock 工具它更专业、配置能力更强。注意镜像版本最好使用具体版本号如mysql:8.0.36而不是latest标签。这能保证每次构建的环境完全一致避免因基础镜像更新引入意外变更。2.3 目录结构规划一个清晰的项目结构是良好维护性的开端。我建议的目录结构如下test-env-docker-compose/ ├── docker-compose.yml # 核心编排文件 ├── .env # 环境变量配置文件可选用于敏感信息 ├── config/ │ ├── mysql/ │ │ └── my.cnf # MySQL 自定义配置文件 │ └── redis/ │ └── redis.conf # Redis 自定义配置文件 ├── data/ # 挂载的数据目录通常被 .gitignore │ ├── mysql/ # MySQL 数据卷 │ └── redis/ # Redis 数据卷 ├── init-scripts/ # 初始化脚本目录 │ └── init-db.sql # MySQL 初始建表、用户脚本 └── mockserver/ └── expectations.json # MockServer 的期望规则配置文件这个结构将配置、数据、初始化脚本和核心定义文件分离非常清晰。data目录通常会被加入.gitignore因为里面是运行时产生的数据不应该纳入版本控制。3. 核心细节解析与实操要点3.1 Docker Compose 文件深度解析下面我们来逐部分拆解docker-compose.yml这个核心文件理解每一行配置的意图。version: 3.8 # 指定 Compose 文件格式版本 services: mysql: image: mysql:8.0.36 container_name: test-mysql restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: rootpassword123 # 建议通过 .env 文件注入 MYSQL_DATABASE: test_db MYSQL_USER: tester MYSQL_PASSWORD: testpass123 ports: - 3306:3306 volumes: - ./data/mysql:/var/lib/mysql:rw - ./config/mysql/my.cnf:/etc/mysql/conf.d/custom.cnf:ro - ./init-scripts:/docker-entrypoint-initdb.d:ro networks: - test-network healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -u, root, -p$$MYSQL_ROOT_PASSWORD] interval: 10s timeout: 5s retries: 5 start_period: 30s redis: image: redis:7.2.4-alpine container_name: test-redis restart: unless-stopped command: redis-server /usr/local/etc/redis/redis.conf --appendonly yes ports: - 6379:6379 volumes: - ./data/redis:/data:rw - ./config/redis/redis.conf:/usr/local/etc/redis/redis.conf:ro networks: - test-network healthcheck: test: [CMD, redis-cli, ping] interval: 10s timeout: 5s retries: 5 mock-server: image: mockserver/mockserver:latest container_name: test-mockserver restart: unless-stopped ports: - 1080:1080 volumes: - ./mockserver/expectations.json:/config/expectations.json:ro environment: MOCKSERVER_INITIALIZATION_JSON_PATH: /config/expectations.json MOCKSERVER_LOG_LEVEL: INFO networks: - test-network depends_on: mysql: condition: service_healthy redis: condition: service_healthy networks: test-network: driver: bridge关键配置解读版本与网络version: 3.8是一个较新且功能稳定的版本。我们显式定义了一个名为test-network的桥接网络三个服务都加入其中它们可以通过服务名mysqlredismock-server直接互相访问。MySQL 服务environment: 这里设置了 root 密码、默认数据库和用户。强烈建议将密码等敏感信息移入.env文件然后在 Compose 文件中用${MYSQL_ROOT_PASSWORD}引用。volumes: 挂载了三处。数据卷./data/mysql:/var/lib/mysql持久化数据库文件。配置卷./config/mysql/my.cnf:/etc/mysql/conf.d/custom.cnf注入自定义配置比如调整字符集、时区等。MySQL 会自动读取conf.d目录下的.cnf文件。初始化脚本卷./init-scripts:/docker-entrypoint-initdb.d这是 MySQL 官方镜像的一个特性容器首次启动时会自动执行该目录下所有的.sh.sql.sql.gz文件。我们可以在这里放建表、初始化数据的 SQL 脚本。healthcheck: 定义了健康检查。Compose 会定期执行mysqladmin ping命令只有健康检查通过依赖它的服务如mock-server的depends_on才会启动。这避免了服务启动顺序问题。Redis 服务command: 覆盖了默认的启动命令指定了配置文件路径并开启了 AOF 持久化 (--appendonly yes)。volumes: 挂载了数据卷和配置文件。Redis 的配置需要我们自己准备一个redis.conf文件可以从官网示例或镜像默认配置复制修改。healthcheck: 使用redis-cli ping来检查 Redis 是否就绪。Mock Server 服务depends_on: 这是关键。它告诉 Composemock-server依赖于mysql和redis并且要等到它们都healthy健康状态后才启动。这确保了 Mock Server 启动时数据库和缓存已经是可用的。volumes和environment: 将本地的expectations.json配置文件挂载到容器内并通过环境变量告诉 MockServer 使用这个文件进行初始化。这样我们就能版本化管理所有的 Mock 规则。3.2 配置文件与初始化脚本准备光有 Compose 文件还不够我们需要准备那些被挂载的配置和脚本。1. MySQL 自定义配置 (config/mysql/my.cnf)[mysqld] character-set-server utf8mb4 collation-server utf8mb4_unicode_ci default-time-zone 08:00 max_connections 500 # 禁用 ONLY_FULL_GROUP_BY方便测试生产环境慎用 sql_mode STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION [client] default-character-set utf8mb4这个配置设置了 UTF8MB4 字符集支持emoji、东八区时区并调整了 SQL 模式。你可以根据测试需要增减配置。2. Redis 自定义配置 (config/redis/redis.conf)可以从 Redis 官网下载一个默认配置然后修改关键几项即可bind 0.0.0.0 # 允许所有网络接口连接在容器内安全 protected-mode no # 关闭保护模式允许外部连接在容器网络内 port 6379 daemonize no # 必须为 no否则容器会立即退出 appendonly yes # 开启 AOF 持久化 requirepass yourredispassword # 设置密码可选如果设置应用连接时需提供对于测试环境protected-mode和密码可以简化但了解如何配置是有必要的。3. MySQL 初始化脚本 (init-scripts/init-db.sql)-- 创建额外的数据库 CREATE DATABASE IF NOT EXISTS app_db; -- 为特定数据库创建用户和授权更安全的方式 GRANT ALL PRIVILEGES ON app_db.* TO tester%; FLUSH PRIVILEGES;这个脚本会在 MySQL 容器第一次启动时执行。你可以在这里创建业务需要的数据库、表结构、初始数据等。4. MockServer 期望配置 (mockserver/expectations.json)这是一个 JSON 数组每个元素定义了一条 Mock 规则。[ { httpRequest: { method: GET, path: /api/v1/user/.* }, httpResponse: { statusCode: 200, headers: { Content-Type: [application/json] }, body: { json: { id: 1, name: Mock User, email: mockexample.com } } } }, { httpRequest: { method: POST, path: /api/v1/order }, httpResponse: { statusCode: 201, headers: { Content-Type: [application/json] }, body: { json: { orderId: ORD-123456, status: created } }, delay: { timeUnit: MILLISECONDS, value: 500 } } } ]这个配置定义了两条规则一是所有匹配/api/v1/user/路径的 GET 请求都返回一个固定的用户信息二是对/api/v1/order的 POST 请求在延迟 500 毫秒后返回一个创建成功的订单响应。MockServer 的功能非常强大你可以定义复杂的条件匹配和响应链。3.3 环境变量与敏感信息管理直接在docker-compose.yml里写密码是极不安全的尤其是当这个文件需要提交到 Git 仓库时。正确做法是使用.env文件。创建.env文件并加入.gitignore# MySQL MYSQL_ROOT_PASSWORDYourStrongRootPass!123 MYSQL_USER_PASSWORDYourTestUserPass!456 # Redis (如果配置了密码) REDIS_PASSWORDYourRedisPass!789然后在docker-compose.yml中引用environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_PASSWORD: ${MYSQL_USER_PASSWORD} # 对应 MYSQL_USER 的密码这样敏感信息就与编排文件解耦了。团队新成员克隆项目后只需要复制一份.env.example模板不含真实密码重命名为.env并填入自己的密码即可。4. 实操过程与核心环节实现4.1 环境搭建与启动全流程假设你已经安装了 Docker 和 Docker Composev2 版本现在通常作为 Docker Desktop 的一部分命令是docker compose注意中间没有短横线。步骤一克隆或创建项目mkdir test-env-docker-compose cd test-env-docker-compose # 创建前面提到的所有目录和文件 mkdir -p config/mysql config/redis data/mysql data/redis init-scripts mockserver步骤二编写配置文件将前面章节提供的docker-compose.yml、my.cnf、redis.conf、init-db.sql、expectations.json和.env文件内容分别写入对应位置。步骤三启动所有服务这是最激动人心的一步只需要一个命令docker compose up -d-d参数代表“分离模式”让服务在后台运行。执行后你会看到 Compose 依次执行创建网络test-env-docker-compose_test-networkCompose 会默认添加项目名前缀。拉取所需的镜像如果本地没有。按照依赖顺序启动容器先启动mysql和redis等待它们的健康检查通过后再启动mock-server。步骤四验证服务状态docker compose ps这个命令会列出当前项目下的所有容器及其状态Up、Exit等。如果一切正常三个服务的状态都应该是Up。 你还可以查看特定服务的日志docker compose logs mysql # 查看 MySQL 启动日志 docker compose logs -f mock-server # 持续查看 MockServer 日志-f 跟随步骤五连接测试MySQL: 使用你的数据库客户端如 MySQL Workbench, DBeaver, 命令行连接localhost:3306用户名tester密码为你设置的密码数据库test_db或app_db。Redis: 使用redis-cli或 Another Redis Desktop Manager 连接localhost:6379。如果配置了密码需要先执行AUTH yourpassword。Mock Server: 打开浏览器或使用curl访问http://localhost:1080/api/v1/user/1你应该能收到在expectations.json中定义的 JSON 响应。4.2 日常使用与维护操作环境启动后日常开发测试中你会频繁用到以下命令停止所有服务docker compose down。这会停止并移除所有容器、网络默认创建的网络但不会删除数据卷所以你的 MySQL 和 Redis 数据下次启动时还在。停止并清理所有资源包括数据卷docker compose down -v。警告这会删除所有挂载的数据卷数据将永久丢失仅在需要彻底重置环境时使用。重启某个服务docker compose restart mysql。重新构建并启动服务如果你修改了 Dockerfile本项目没有或想强制使用最新镜像可以用docker compose up -d --build。查看服务日志docker compose logs [service-name]。进入容器内部docker compose exec mysql bash进入 MySQL 容器的 bash shell。这对于调试、执行一些临时命令非常有用。4.3 与应用项目的集成这个测试环境如何被你真正的后端或前端项目使用呢非常简单只需要修改你项目的配置文件。后端项目如 Spring Boot配置示例 (application.yml)spring: datasource: url: jdbc:mysql://localhost:3306/app_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: tester password: ${DB_PASSWORD:testpass123} # 建议也从环境变量读取 redis: host: localhost port: 6379 password: ${REDIS_PASSWORD:} # 如果 Redis 有密码 database: 0 # 如果你的应用需要调用被 Mock 的接口 external: api: base-url: http://localhost:1080现在你的应用启动时就会连接到这个 Docker Compose 管理的本地测试环境了。5. 常见问题与排查技巧实录即使按照步骤操作也可能会遇到一些问题。这里记录了几个我踩过的坑和解决方案。5.1 端口冲突问题这是最常见的问题。错误信息通常是Bind for 0.0.0.0:3306 failed: port is already allocated。原因你的宿主机上已经有其他进程可能是另一个 MySQL 实例或者之前的 Docker 容器没清理干净占用了 3306、6379 或 1080 端口。排查与解决查找占用端口的进程Linux/macOS:sudo lsof -i :3306或sudo netstat -tulpn | grep :3306Windows:netstat -ano | findstr :3306然后根据 PID 在任务管理器中查找进程。解决方案停止冲突进程如果是不需要的进程就停掉它。修改 Compose 端口映射这是更推荐的做法避免影响宿主机其他服务。将docker-compose.yml中的ports映射修改为其他端口例如ports: - 3307:3306 # 宿主机3307映射到容器3306 - 6380:6379 - 1081:1080然后你的应用连接配置也要相应改为localhost:3307等。5.2 数据卷权限问题在 Linux 或 macOS 上你可能会遇到 MySQL 或 Redis 容器启动失败日志报错Permission denied。原因Docker 容器内的进程通常以mysql或redis用户运行UID 通常是 999 或 1000没有权限写入你宿主机挂载的./data/mysql或./data/redis目录。解决最彻底的方法在启动容器前确保宿主机目录对 Docker 的运行时用户有足够权限。可以递归修改目录所有者假设你的 Docker 守护进程以root运行但容器内用户是999sudo chown -R 999:999 ./data/mysql sudo chown -R 1001:1001 ./data/redis # Redis 镜像常用 1001注意用户IDUID因镜像而异最好通过docker run --rm mysql:8.0 id mysql命令查看。临时或快速方法在docker-compose.yml中为服务添加user: root指令让容器以 root 身份运行不推荐用于生产但测试环境可临时使用。推荐做法一劳永逸在宿主机创建目录时就设置较宽松的权限比如mkdir -p data chmod -R 777 data。注意777 权限有安全风险仅适用于纯本地、隔离的测试环境。5.3 服务启动顺序与依赖问题虽然我们使用了depends_on和healthcheck但有时应用启动太快依赖的服务如 MySQL虽然容器状态是Up但内部进程可能还没完全准备好接受连接。现象mock-server日志显示连接 MySQL 被拒绝。解决强化健康检查确保健康检查命令是有效的并且start_period给了服务足够的启动时间如 MySQL 的 30秒。应用层重试最健壮的方式是在你的应用程序中或 MockServer 的初始化逻辑里加入连接重试机制。例如在连接数据库的代码里捕获异常并循环重试几次每次等待几秒。使用wait-for-it或dockerize工具这是更通用的解决方案。可以创建一个自定义的启动脚本或使用一个轻量级工具在启动主进程前先检测依赖服务的端口是否真正可读。这需要你编写自定义的 Dockerfile 来构建镜像稍微复杂一些。5.4 镜像拉取慢或失败原因Docker Hub 在国内访问可能不稳定。解决配置国内镜像加速器修改 Docker 守护进程配置/etc/docker/daemon.json或 Docker Desktop 设置。{ registry-mirrors: [ https://registry.docker-cn.com, https://hub-mirror.c.163.com, https://mirror.baidubce.com ] }修改后重启 Docker。使用代理如果你有可用的网络代理可以在 Docker 客户端或宿主机环境变量中配置。5.5 MockServer 规则不生效现象访问http://localhost:1080/api/v1/user/1返回 404 或其他非预期响应。排查检查日志docker compose logs mock-server查看是否有错误信息或者请求是否被正确记录。验证配置路径确认expectations.json文件路径正确且容器内的环境变量MOCKSERVER_INITIALIZATION_JSON_PATH指向了挂载后的正确路径。检查 JSON 语法JSON 文件必须严格符合语法一个多余的逗号都会导致解析失败。可以使用在线 JSON 校验工具检查。动态管理规则MockServer 提供了管理端口默认 1080 同服务端口和 REST API你可以动态添加、删除、验证期望规则。例如通过curl命令直接向http://localhost:1080/mockserver/expectation发送 PUT 请求来添加一条规则这比修改文件重启容器更灵活适合调试。我个人在实际操作中的体会是这套环境搭建一次受益整个项目周期。最大的收获不是节省了多少安装时间而是带来了环境的确定性和可复现性。新同事 onboarding再也不用陪着他折腾一天环境了一句“把项目里的docker-compose up -d跑一下”就能搞定基础依赖。团队协作时因为底层服务完全一致联调 bug 的效率也高了很多。最后一个小技巧是可以把这套docker-compose.yml和配置放到项目的根目录或者一个docker子目录下并写一个简短的README.md说明使用方法这将成为你们团队技术栈中一个非常扎实的基础设施。