Docker Compose
这一课你将能做到
- 把 Spring Boot + MySQL + Redis 三个容器写进一份
docker-compose.yml,用 docker compose up -d 一键拉起整个后端环境
- 用 healthcheck + depends_on 解决「MySQL 还没就绪 Spring Boot 就启动」的经典时序问题
- 熟练使用 compose 的日常命令:up/down/logs/ps/exec,并掌握改代码后的标准更新流程
为什么要学
前 9 课里,为了跑通一个后端环境你敲了多少条命令:建网络、起 MySQL(带卷)、起 Redis(带密码)、起应用(带一长串 -e)……每次重启机器都要来一遍,参数靠翻历史记录。Compose 把这些全部固化成一个 YAML 文件:环境即代码,一键复现,提交进 git 团队共享。
生产上 compose 同样有用武之地:单机部署的中小项目(一台云服务器跑全套)用它管理服务最省心;它也是从「手动 docker run」迈向 K8s 之前的最后一站——你在这课养成的「服务、健康检查、依赖编排」概念,全部可以平移到 K8s。
先建立图像
一份 compose 文件 = 你前 9 课所有 docker run 参数的声明式版本:
命令式(前面课程): 声明式(compose):
docker network create app-net services: 定义每个容器
docker run ... mysql → volumes: 定义数据卷
docker run ... redis → networks: 定义网络
docker run ... -e ... app → depends_on: 定义启动顺序与就绪条件
docker compose up -d = 按声明一次性对齐现实
文件结构五要素:
services: # 每个容器一个服务,服务名即 DNS 名
volumes: # 声明 named volume
networks: # 声明网络
environment: # 环境变量注入
depends_on: # 启动依赖与就绪条件
跟着做
步骤 1:编写 docker-compose.yml
放在项目根目录(与 Dockerfile 同级)。这份配置直接可用:
services:
mysql:
image: mysql:8.0
container_name: app-mysql
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: appdb
volumes:
- mysql-data:/var/lib/mysql
networks:
- app-net
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"]
interval: 5s
timeout: 3s
retries: 12
start_period: 30s
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: app-redis
command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}"]
networks:
- app-net
volumes:
- redis-data:/data
restart: unless-stopped
app:
build: . # 用当前目录 Dockerfile 构建
image: demo-app:1.0 # 构建后的镜像名
container_name: app-backend
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/appdb
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD}
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PASSWORD: ${REDIS_PASSWORD}
depends_on:
mysql:
condition: service_healthy # 等 MySQL 健康检查通过才启动
redis:
condition: service_started
networks:
- app-net
restart: unless-stopped
volumes:
mysql-data:
redis-data:
networks:
app-net:
注意:服务间连接用服务名(mysql、redis),不是 container_name。Compose 自动创建网络并内置 DNS。
步骤 2:编写 .env 管理密码
MYSQL_ROOT_PASSWORD=StrongPass_2024
REDIS_PASSWORD=RedisDev_2024
.gitignore 里加上 .env,另提交一份 .env.example(只有 key 无 value)给同事参考。密码永远不进 git。
步骤 3:一键启动
docker compose up -d
docker compose ps # 三个服务都应显示 running,mysql 显示 (healthy)
docker compose logs -f app # 盯着 Spring Boot 启动日志
curl http://localhost:8080/actuator/health
第一次会自动构建 app 镜像。depends_on: service_healthy 保证了 MySQL 完全就绪后 app 才启动——本课最关键的一处配置。
步骤 4:常用命令速练
docker compose logs --tail 50 mysql # 看单个服务日志
docker compose exec mysql mysql -uroot -pStrongPass_2024 # 进 MySQL
docker compose restart app # 重启单个服务
docker compose stop # 全部停止(容器保留)
docker compose start # 全部启动
docker compose down # 停止并删除容器与网络(卷保留!)
docker compose down -v # 连卷一起删(数据没了,生产禁用)
步骤 5:改代码后的标准更新流程
mvn clean package -DskipTests # ① 本地(或 CI)出新 jar
docker compose up -d --build app # ② 只重建并替换 app 服务,mysql/redis 不动
docker compose logs -f app # ③ 验证
up -d --build 只会影响配置或镜像发生变化的服务——这也是 compose 的编排价值:改一个服务,不用重建整个环境。
步骤 6:多环境配置(了解机制)
Compose 支持 override 合并:docker-compose.yml 放基础配置,docker-compose.override.yml 自动叠加开发差异;或显式多文件:
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d
生产差异(资源限制、log 策略)放 override 文件,基础文件保持团队一致。
讲清楚要点
- 服务名 = DNS 名 = Spring Boot 里的主机名:
SPRING_DATASOURCE_URL 写 jdbc:mysql://mysql:3306/...,mysql 是服务名。container_name 只是容器别名,跨服务连接一律用服务名。
- depends_on 有两种深度:默认只管「启动顺序」(容器进程起了就算);
condition: service_healthy 才管「就绪顺序」(健康检查通过才算)。数据库类服务必须用后者,否则 Spring Boot 大概率抢跑失败。
- down 与 down -v 的天壤之别:down 删容器和网络、保留卷(数据安全);down -v 连卷一起删(数据清零)。把
down -v 当成 drop database 级别的危险操作对待。
- restart: unless-stopped 的工程意义:进程崩溃自动拉起、宿主机重启后自动恢复(除非你手动 stop 过)。单机生产部署的可用性底线。
- compose 会给资源加项目前缀:目录名(如
demo)成为项目名,卷名实际是 demo_mysql-data、网络是 demo_app-net。换目录执行命令会操作到「另一个项目」——在 compose 文件所在目录操作最稳。
- Compose 是单机编排:它不知道另一台服务器的存在。多机、自动扩缩容、滚动发布,是 K8s 的领域(第 12 课的桥就架在这里)。
你可能会懵的地方
-
MySQL 健康检查为什么用 mysqladmin ping?
它探活 MySQL 进程是否响应,成本低且不需要业务表。更严格的场景可以改成执行一条 SELECT 1。注意 start_period 要给足首次初始化时间(导入初始数据可能要几十秒)。
-
app 启动还是报连不上 MySQL,怎么办?
三步排查:docker compose ps 看 mysql 是否 (healthy);docker compose logs app 看连接错误细节;进 app 容器验证 DNS:docker compose exec app getent hosts mysql。高频根因是 healthcheck 没配或密码变量没注入(.env 缺失)。
-
build: . 和 image: 同时写是什么意思?
build 指定怎么构建;image 给构建结果命名。两者同写时,up --build 会构建并打上该镜像名;不写 image 则自动命名为 项目名-服务名。
-
为什么我换了目录,compose 说找不到服务?
项目按目录绑定。切到别的目录执行 docker compose down 操作的是那个目录的项目(或报无服务)。固定在 yml 所在目录操作,或用 -p 项目名 显式指定。
-
compose 能做生产部署吗?
单机中小项目完全可以(配合 restart 策略 + 卷备份 + 日志限制),很多公司内部系统就这么跑。需要多机高可用、滚动发布、自动扩缩容时,再上 K8s——compose 里的 service/volume/healthcheck 概念全部平移。
错法对照
| 错法 | 问题 | 正解 |
|---|
| depends_on 只写服务名 | 只保证顺序,MySQL 未就绪 app 抢跑崩 | condition: service_healthy + healthcheck |
Spring Boot URL 写 jdbc:mysql://app-mysql:...(container_name) | 跨服务应该用服务名 | 写服务名 mysql |
| 密码明文写进 compose 并提交 git | 凭据泄漏 | .env + .gitignore + .env.example |
以为 down 会删数据,或以为 down 后数据还在就放心 down -v | 前者过度担心,后者真删数据 | 记住:down 保卷,down -v 删卷 |
| YAML 缩进用 Tab | 解析报错 | 一律空格,建议编辑器装 yaml 校验 |
改代码后 down + up 全量重建 | MySQL/Redis 也重启,浪费时间 | up -d --build app 只更新 app |
卡点急救
docker compose 命令不存在:老版用法是 docker-compose(带横线)。装了 docker-compose-plugin 后统一用 docker compose(空格),docker compose version 验证。
- up 时报变量为空警告:
.env 文件不在 compose 文件同目录,或变量名拼写不一致。docker compose config 可以预渲染最终配置,直接看变量是否注入。
- app 反复重启(Restarting 状态):通常是连不上数据库崩了又被 restart 拉起。先看
docker compose logs app 的第一次报错,再按「步骤 6 排查三步」走。
- 端口 8080 被宿主机占用:compose 的 ports 改成
"8081:8080",或停掉占用进程(netstat -ano | findstr 8080)。
- 误执行了 down -v 数据没了:只剩备份可救——执行第 8 课的恢复流程把备份卷挂回来。养成「down 前看一眼命令结尾」的习惯。
面试常问
-
Compose 解决什么问题?和直接 docker run 比优势在哪?
把多容器拓扑(服务、网络、卷、依赖、环境变量)声明进一个 YAML,实现环境即代码:一键 up/down、版本化进 git、团队一键复现。对比手敲 docker run:无遗漏参数、可重复、可审查。
-
depends_on 如何保证 MySQL 就绪后应用再启动?
depends_on 默认只控制容器启动顺序;需要配合服务端 healthcheck + condition: service_healthy,让依赖方等健康检查通过后再启动。应用侧再加连接重试/重启策略做双保险。
-
Compose 和 Kubernetes 的定位区别?
Compose 面向单机编排,简单直接,适合开发环境与单机部署;K8s 面向多机集群,提供调度、自愈、扩缩容、滚动发布、服务发现。K8s 没有「主机名直连」而是 Service 抽象,但健康检查、声明式配置思想一脉相承。
-
生产上如何管理 compose 的敏感配置?
密码放 .env(不入 git)或部署机手写的环境文件;更严格用 docker secrets(swarm)或外部配置中心(Nacos/Vault)。镜像里出现任何明文密钥都是事故。
自测三问
- 写出「等 MySQL 健康后才启动 app」的完整 compose 配置片段(含 healthcheck)。
docker compose down 和 down -v 分别删除什么?restart: unless-stopped 补上了什么能力?
- 改了一行 Java 代码,写出把新版本部署到 compose 环境的完整命令序列。
小结一句话
Compose 把「一堆 docker run 参数 + 网络 + 卷 + 依赖时序」收进一份可版本化的 YAML——服务名即 DNS、healthcheck 管就绪、down 不碰卷,是单机编排的完整答案。
延伸
- Compose 官方文档
- 下一课预告:容器安全基础——非 root 运行、密钥管理、镜像扫描,把「能跑」升级成「敢上生产」。