欢迎你来读这篇博客。
这篇文章专门写 Docker Compose,不再把它当作 Docker 基础知识里的一小节,而是把它当成一个“单机多容器应用编排工具”来系统讲清楚。
如果说 Docker 解决的是:
一个容器怎么跑起来?
那么 Docker Compose 解决的是:
一组互相依赖的容器,如何用一份声明式配置稳定、可重复、可维护地跑起来?
对于 Java 后端开发来说,一个服务通常不会孤零零地运行。它旁边大概率还会有 MySQL、Redis、Nginx、MQ、ES、Prometheus、Grafana、Adminer 等组件。如果每个容器都靠 docker run 手写命令启动,短期能跑,长期就是灾难。
Docker Compose 的价值就在这里:
把多个容器服务写成一份 compose.yaml
自动创建网络、数据卷、容器
支持服务间通过服务名通信
支持环境变量、健康检查、依赖顺序
支持多环境覆盖配置
支持一键启动、停止、重建、查看日志
适合本地开发、测试环境、CI 环境、单机生产部署
它不是 Kubernetes,也不想假装自己是 Kubernetes。Compose 更像是一个务实工具:不追求“航母级编排”,但非常适合开发、测试、中小型单机部署。说人话就是:别拿大炮打蚊子,也别拿牙签撬地球。
一、为什么需要 Docker Compose? 1. 只用 docker run 的问题 假设我们要部署一个 Spring Boot 应用,依赖 MySQL 和 Redis。如果不用 Compose,可能要写这些命令:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 docker network create mall-net docker run -d \ --name mall-mysql \ --network mall-net \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=123456 \ -e MYSQL_DATABASE=mall \ -v mall-mysql-data:/var/lib/mysql \ mysql:8.0 docker run -d \ --name mall-redis \ --network mall-net \ -p 6379:6379 \ -v mall-redis-data:/data \ redis:7 redis-server --appendonly yes docker run -d \ --name mall-app \ --network mall-net \ -p 8080:8080 \ -e SPRING_PROFILES_ACTIVE=prod \ -e SPRING_DATASOURCE_URL='jdbc:mysql://mall-mysql:3306/mall?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai' \ -e SPRING_DATASOURCE_USERNAME=root \ -e SPRING_DATASOURCE_PASSWORD=123456 \ -e SPRING_DATA_REDIS_HOST=mall-redis \ mall-app:1.0.0
这些命令的问题很明显:
太长,容易写错。
不好版本管理。
迁移到别的机器不方便。
环境变量散落在命令里。
容器启动顺序靠人肉记忆。
数据卷、网络、日志、健康检查都难统一管理。
团队协作时,新人不一定知道完整启动步骤。
而 Compose 可以把这些命令收敛成一份文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 services: app: image: mall-app:1.0.0 ports: - "8080:8080" environment: SPRING_PROFILES_ACTIVE: prod SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mall?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: 123456 SPRING_DATA_REDIS_HOST: redis depends_on: - mysql - redis mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: mall volumes: - mysql-data:/var/lib/mysql redis: image: redis:7 command: ["redis-server" , "--appendonly" , "yes" ] volumes: - redis-data:/data volumes: mysql-data: redis-data:
然后启动:
停止:
这就是 Compose 的第一个价值:把命令式部署变成声明式部署 。
2. Compose 的工作方式 Compose 本质上做了几件事:
读取 compose.yaml
解析出服务、网络、卷、配置、密钥等模型
根据项目名生成资源名称
按依赖关系创建网络和卷
构建或拉取镜像
创建并启动容器
将同一项目下的服务接入同一个默认网络
提供统一的日志、执行命令、停止、重建、清理能力
流程图如下:
flowchart TB
A[compose.yaml] --> B[docker compose 解析配置]
B --> C[生成 Compose Application Model]
C --> D[创建 Network]
C --> E[创建 Volumes]
C --> F[构建或拉取 Images]
F --> G[创建 Containers]
D --> G
E --> G
G --> H[服务运行]
H --> I[logs / exec / restart / down]
3. Compose 适合什么场景? 适合:
本地开发环境
中间件快速搭建
测试环境
自动化集成测试环境
单机部署
演示环境
小型内部系统
开发者个人服务器
CI 中启动依赖服务
不适合:
大规模多节点调度
复杂弹性伸缩
跨机器服务发现
自动故障迁移
多租户资源调度
大型生产集群治理
如果你只是想在一台机器上把应用、MySQL、Redis、Nginx 跑起来,Compose 很顺手。 如果你要治理几百个服务、几十台机器、多可用区、多副本滚动发布,那应该看 Kubernetes。
二、Docker Compose 的核心概念 1. Project 项目 Compose 会把一份 compose.yaml 视为一个项目。项目名会影响资源命名。
默认项目名通常来自当前目录名。例如目录叫:
那么 Compose 创建的资源可能类似:
1 2 3 4 mall-system-app-1 mall-system-mysql-1 mall-system_default mall-system_mysql-data
可以用 -p 指定项目名:
1 docker compose -p mall-prod up -d
也可以在文件里写:
项目名很重要,因为同一台机器上可以同时跑多套同名服务,只要项目名不同:
1 2 3 docker compose -p mall-dev up -d docker compose -p mall-test up -d docker compose -p mall-prod up -d
这三套环境的容器、网络、卷会被隔离开。
2. Service 服务 services 是 Compose 最核心的部分。一个 service 通常对应一类容器。
1 2 3 services: app: image: mall-app:1.0.0
这里的 app 是服务名。服务名非常关键,因为同一个 Compose 网络中,服务名就是 DNS 名称。
例如应用访问 MySQL,不应该写宿主机 IP,而应该写:
因为服务名是 mysql。
3. Container 容器 Service 是声明,Container 是运行实例。
一个服务默认启动一个容器:
也可以扩容某个无状态服务:
1 docker compose up -d --scale app=3
注意,如果服务配置了固定的 container_name,就不适合扩容,因为容器名必须唯一。
4. Network 网络 Compose 默认会为项目创建一个网络。项目内服务默认都加入这个网络。
这意味着:
1 2 3 4 5 services: app: image: mall-app mysql: image: mysql:8.0
app 可以直接访问:
不需要手动写 --network。
5. Volume 数据卷 Compose 可以声明命名数据卷:
然后挂载到服务:
1 2 3 4 5 services: mysql: image: mysql:8.0 volumes: - mysql-data:/var/lib/mysql
这样容器删除后,数据卷还在。
6. Config 和 Secret Compose 也支持 configs 和 secrets,用于挂载配置和敏感文件。
不过要注意:在普通单机 Docker Compose 场景下,secrets 的安全级别不能和 Kubernetes Secret 或云厂商密钥管理服务完全等同。它更像是一种“文件挂载式管理方式”,比把密码写死进镜像好,但不是万能保险箱。
7. Compose 模型总览 flowchart TB
A[Compose Project] --> B[Services]
A --> C[Networks]
A --> D[Volumes]
A --> E[Configs]
A --> F[Secrets]
B --> B1[app]
B --> B2[mysql]
B --> B3[redis]
B --> B4[nginx]
C --> C1[default network]
C --> C2[backend network]
C --> C3[frontend network]
D --> D1[mysql-data]
D --> D2[redis-data]
三、Compose V1、V2 和现代写法 1. docker-compose 和 docker compose 的区别 老版本命令是:
现代推荐命令是:
区别:
命令
说明
docker-compose
老的 Compose V1,独立 Python 工具
docker compose
Compose V2,作为 Docker CLI 插件使用
现在更推荐使用 docker compose。
查看版本:
2. 不再推荐写 version: "3.8" 很多老文章会这样写:
1 2 3 4 version: "3.8" services: app: image: nginx
现代 Compose Specification 中,顶层 version 字段已经主要用于兼容历史格式。新写法可以直接:
1 2 3 services: app: image: nginx
如果你继续写 version,某些新版本 Compose 会提示它已经过时。 所以新文章、新项目、新模板,建议直接省略 version。
3. 文件名建议 推荐文件名:
兼容文件名:
1 2 docker-compose.yml docker-compose.yaml
现在官方文档更常用 compose.yaml,但很多历史项目仍然用 docker-compose.yml。实际项目里两者都能见到。
四、Compose 文件结构详解 1. 最小可运行示例 1 2 3 4 5 services: nginx: image: nginx:latest ports: - "8080:80"
启动:
访问:
1 curl http://localhost:8080
停止并删除容器和默认网络:
2. 标准结构 一个较完整的 Compose 文件通常长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 name: mall-dev services: app: image: mall-app:1.0.0 build: context: . dockerfile: Dockerfile ports: - "8080:8080" environment: SPRING_PROFILES_ACTIVE: dev env_file: - .env volumes: - ./logs:/app/logs networks: - backend depends_on: mysql: condition: service_healthy restart: unless-stopped mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: mall volumes: - mysql-data:/var/lib/mysql networks: - backend networks: backend: volumes: mysql-data:
核心层级:
顶层字段
说明
name
项目名
services
服务定义,最核心
networks
网络定义
volumes
数据卷定义
configs
配置定义
secrets
密钥定义
五、services 详解 1. image 使用现成镜像:
1 2 3 services: redis: image: redis:7
建议固定版本,不建议生产长期使用 latest:
或者更严格地固定 digest:
1 image: redis@sha256:xxxx
2. build 从 Dockerfile 构建镜像:
1 2 3 4 5 6 services: app: build: context: . dockerfile: Dockerfile image: mall-app:1.0.0
字段含义:
字段
说明
context
构建上下文目录
dockerfile
Dockerfile 文件路径
args
构建参数
target
多阶段构建目标阶段
示例:
1 2 3 4 5 6 7 8 9 services: app: build: context: . dockerfile: Dockerfile target: runner args: APP_VERSION: 1.0 .0 image: mall-app:1.0.0
对应 Dockerfile:
1 2 3 4 5 6 7 8 9 10 11 12 FROM maven:3.9 -eclipse-temurin-17 AS builderWORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM eclipse-temurin:17 -jre AS runnerWORKDIR /app COPY --from=builder /build/target/*.jar app.jar EXPOSE 8080 ENTRYPOINT ["java" , "-jar" , "app.jar" ]
3. container_name 可以指定容器名:
1 2 3 4 services: app: image: mall-app:1.0.0 container_name: mall-app
但是不建议滥用。原因:
Compose 默认会根据项目名生成容器名,避免冲突。
写死 container_name 后,同一台机器不能轻松启动多套环境。
写死容器名后,不适合使用 --scale 扩容。
本地调试可以用,团队项目和生产部署建议谨慎。
4. ports 端口发布,把容器端口映射到宿主机。
1 2 3 4 5 services: nginx: image: nginx ports: - "8080:80"
含义:
只监听本机:
1 2 ports: - "127.0.0.1:8080:80"
长语法:
1 2 3 4 5 ports: - target: 80 published: 8080 protocol: tcp mode: host
什么时候需要 ports?
需要宿主机访问容器
需要外部机器访问容器
需要浏览器访问服务
需要暴露 Nginx、应用 HTTP 接口
什么时候不需要?
只给 Compose 内部其他服务访问
例如 app 访问 mysql,mysql 不一定要映射 3306:3306
5. expose expose 只声明容器对内部网络暴露端口,不发布到宿主机。
1 2 3 4 5 services: app: image: mall-app:1.0.0 expose: - "8080"
它更多是文档化表达,服务间通信主要靠同网络和服务名。
6. environment 设置容器环境变量:
1 2 3 4 5 6 services: app: image: mall-app:1.0.0 environment: SPRING_PROFILES_ACTIVE: prod JAVA_OPTS: "-Xms256m -Xmx512m"
也可以写列表形式:
1 2 3 environment: - SPRING_PROFILES_ACTIVE=prod - JAVA_OPTS=-Xms256m -Xmx512m
更推荐 map 形式,可读性更好。
7. env_file 从文件中读取环境变量并注入容器:
1 2 3 4 5 services: app: image: mall-app:1.0.0 env_file: - ./app.env
app.env:
1 2 SPRING_PROFILES_ACTIVE=prod JAVA_OPTS=-Xms256m -Xmx512m
注意:env_file 是给容器注入环境变量,不完全等同于 Compose 文件变量插值用的 .env。这是很多人踩坑的地方。
8. command 覆盖镜像默认命令。
Redis 示例:
1 2 3 4 services: redis: image: redis:7 command: ["redis-server" , "--appendonly" , "yes" ]
Nginx 示例:
1 2 3 4 services: nginx: image: nginx command: ["nginx" , "-g" , "daemon off;" ]
9. entrypoint 覆盖镜像入口。
1 2 3 4 5 services: app: image: mall-app:1.0.0 entrypoint: ["sh" , "-c" ] command: ["java $JAVA_OPTS -jar app.jar" ]
一般不建议在 Compose 里随便覆盖 entrypoint,除非你非常清楚镜像原本怎么启动。
10. volumes 挂载数据卷或宿主机目录。
命名卷:
1 2 3 4 5 6 7 8 services: mysql: image: mysql:8.0 volumes: - mysql-data:/var/lib/mysql volumes: mysql-data:
绑定宿主机目录:
1 2 3 4 5 6 services: nginx: image: nginx volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./html:/usr/share/nginx/html:ro
建议:
数据库数据用命名卷。
配置文件可以用 bind mount。
本地开发代码热更新可以用 bind mount。
生产环境不要把应用代码目录挂进去,生产应使用构建好的镜像。
11. networks 指定服务加入哪些网络。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 services: app: image: mall-app networks: - frontend - backend mysql: image: mysql:8.0 networks: - backend networks: frontend: backend:
这样 app 可以同时连接前端网络和后端网络,而 mysql 只在后端网络中。
12. depends_on 声明服务依赖关系。
短语法:
1 2 3 4 5 6 services: app: image: mall-app depends_on: - mysql - redis
这表示 mysql 和 redis 会先创建,再创建 app。
但是要注意:短语法通常只保证“启动顺序”,不保证 MySQL 已经可以接受连接。MySQL 容器进程启动了,不代表数据库初始化完成。这个坑很经典,堪称 Compose 入门税。
更推荐结合健康检查:
1 2 3 4 5 6 7 8 services: app: image: mall-app depends_on: mysql: condition: service_healthy redis: condition: service_healthy
13. healthcheck 健康检查用于判断服务是否真正可用。
MySQL:
1 2 3 4 5 6 7 8 9 10 11 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 healthcheck: test: ["CMD" , "mysqladmin" , "ping" , "-h" , "localhost" , "-uroot" , "-p123456" ] interval: 5s timeout: 3s retries: 20 start_period: 30s
Redis:
1 2 3 4 5 6 7 8 services: redis: image: redis:7 healthcheck: test: ["CMD" , "redis-cli" , "ping" ] interval: 5s timeout: 3s retries: 20
Spring Boot:
1 2 3 4 5 6 7 8 9 services: app: image: mall-app:1.0.0 healthcheck: test: ["CMD-SHELL" , "wget -qO- http://localhost:8080/actuator/health | grep UP || exit 1" ] interval: 10s timeout: 3s retries: 10 start_period: 30s
如果镜像里没有 wget 或 curl,健康检查会失败。要么换一种检查方式,要么在镜像中加入工具。
14. restart 容器异常退出后的重启策略。
1 2 3 4 services: app: image: mall-app:1.0.0 restart: unless-stopped
常用值:
策略
说明
no
默认,不自动重启
always
总是重启
unless-stopped
除非手动停止,否则自动重启
on-failure
非 0 状态码退出时重启
服务类应用推荐:
15. logging 控制容器日志大小,避免日志撑爆磁盘。
1 2 3 4 5 6 7 8 services: app: image: mall-app:1.0.0 logging: driver: json-file options: max-size: "100m" max-file: "3"
这非常重要。线上磁盘爆满,很多时候不是业务数据爆了,而是日志悄悄变成了“数字垃圾山”。
16. resource 限制 Compose 可以限制容器资源。
1 2 3 4 5 services: app: image: mall-app:1.0.0 mem_limit: 768m cpus: 1.0
Java 应用要特别注意:
1 2 3 environment: JAVA_OPTS: "-Xms256m -Xmx512m" mem_limit: 768m
不要容器限制 512MB,JVM 也设置 -Xmx512m。因为 JVM 堆外内存、线程栈、元空间也要占内存。
17. working_dir 和 user 指定工作目录:
指定用户运行:
生产环境建议镜像本身使用非 root 用户,而不是完全依赖 Compose 层覆盖。
六、Docker Compose 常用命令 1. 启动服务 前台启动:
后台启动:
构建并启动:
1 docker compose up -d --build
强制重建容器:
1 docker compose up -d --force-recreate
不启动依赖,只重建某个服务:
1 docker compose up -d --no-deps app
2. 停止和删除 停止服务但不删除容器:
启动已停止服务:
停止并删除容器、默认网络:
停止并删除容器、网络、数据卷:
down -v 会删除数据卷。数据库环境不要随手敲,除非你真的想体验“删库跑路模拟器”。
3. 查看服务状态
查看运行进程:
查看项目列表:
4. 查看日志 查看所有服务日志:
实时日志:
查看某个服务日志:
1 docker compose logs -f app
只看最近 200 行:
1 docker compose logs --tail =200 -f app
5. 进入容器 1 docker compose exec app sh
如果镜像里有 bash:
1 docker compose exec app bash
进入 MySQL:
1 docker compose exec mysql mysql -uroot -p123456
进入 Redis:
1 docker compose exec redis redis-cli
6. 临时运行命令 1 docker compose run --rm app java -version
exec 和 run 的区别:
命令
作用
exec
在已经运行的容器中执行命令
run
基于服务配置新建一个临时容器执行命令
7. 构建、拉取、推送镜像 构建全部服务:
构建指定服务:
1 docker compose build app
拉取镜像:
推送镜像:
8. 校验和展开配置 这是非常重要的命令:
它会输出最终生效的 Compose 配置,包括变量插值、多文件合并后的结果。
检查环境变量插值:
1 docker compose config --environment
多环境部署前建议先执行:
1 docker compose -f compose.yaml -f compose.prod.yaml config
别等容器炸了才发现 YAML 缩进写错了。YAML 这个东西,看着像配置,生气起来像玄学。
七、环境变量与配置管理 Compose 里有几类变量,很容易混:
Compose 文件变量插值
容器环境变量
.env 文件
env_file
Shell 环境变量
应用内部配置
1. Compose 变量插值 Compose 支持类似 Bash 的变量语法:
1 2 3 4 5 services: app: image: mall-app:${APP_VERSION} ports: - "${APP_PORT}:8080"
.env:
1 2 APP_VERSION=1.0.0 APP_PORT=8080
启动:
最终等价于:
1 2 3 image: mall-app:1.0.0 ports: - "8080:8080"
2. 默认值 1 image: mall-app:${APP_VERSION:-latest}
如果 APP_VERSION 不存在或为空,则使用 latest。
3. 必填值 1 image: mall-app:${APP_VERSION:?APP_VERSION is required}
如果没有设置 APP_VERSION,Compose 会直接报错。
这在生产部署时非常好用,避免误用默认值。
4. .env 文件 .env 通常放在 compose.yaml 同级目录。
1 2 3 4 5 6 PROJECT_NAME=mall-prod APP_VERSION=1.0.3 APP_PORT=8080 MYSQL_ROOT_PASSWORD=change-me MYSQL_DATABASE=mall REDIS_PORT=6379
Compose 文件:
1 2 3 4 5 6 7 name: ${PROJECT_NAME:-mall-dev} services: app: image: mall-app:${APP_VERSION:?APP_VERSION required} ports: - "${APP_PORT:-8080}:8080"
5. Shell 环境变量优先级 如果 Shell 里设置了变量:
1 export APP_VERSION=2.0.0
即使 .env 中是:
Compose 插值时也可能优先使用 Shell 中的值。
所以部署脚本里建议显式打印当前配置:
1 docker compose config | sed -n '1,120p'
6. environment 和 env_file .env 主要用于 Compose 文件插值。environment / env_file 主要用于给容器注入运行时环境变量。
示例:
1 2 3 4 5 6 7 services: app: image: mall-app:${APP_VERSION} env_file: - ./app.env environment: SPRING_PROFILES_ACTIVE: prod
app.env:
1 2 JAVA_OPTS=-Xms256m -Xmx512m LOG_LEVEL=INFO
容器内能看到:
1 docker compose exec app env
7. 环境变量最佳实践 建议:
.env 用于非敏感的项目级变量,例如端口、版本号、项目名。
environment 用于少量明确配置。
env_file 用于较多运行时配置。
密码、Token、私钥不要提交到 Git。
生产环境敏感配置建议使用外部密钥系统,至少也要 .gitignore。
.gitignore:
1 2 3 4 .env *.env .env.* !.env.example
提供 .env.example:
1 2 3 4 5 PROJECT_NAME=mall-dev APP_VERSION=1.0.0 APP_PORT=8080 MYSQL_ROOT_PASSWORD=please-change-me MYSQL_DATABASE=mall
八、服务依赖、启动顺序和健康检查 1. depends_on 的误区 很多人以为:
等价于:
等 MySQL 完全启动好之后,再启动 app。
这不准确。
短语法主要表达创建和启动顺序,不能保证 MySQL 已经完成初始化、建库、加载权限、开始接受连接。
所以你可能看到这种日志:
1 2 Communications link failure Connection refused
但等几秒再重启 app 又好了。这不是玄学,是数据库还没 ready。
2. 正确姿势:depends_on + healthcheck 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 services: app: image: mall-app:1.0.0 depends_on: mysql: condition: service_healthy redis: condition: service_healthy mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 healthcheck: test: ["CMD" , "mysqladmin" , "ping" , "-h" , "localhost" , "-uroot" , "-p123456" ] interval: 5s timeout: 3s retries: 20 start_period: 30s redis: image: redis:7 healthcheck: test: ["CMD" , "redis-cli" , "ping" ] interval: 5s timeout: 3s retries: 20
启动流程:
flowchart TB
A[docker compose up -d] --> B[创建 mysql]
A --> C[创建 redis]
B --> D{mysql healthcheck 通过?}
C --> E{redis healthcheck 通过?}
D -->|yes| F[创建 app]
E -->|yes| F
D -->|no| G[等待 / 重试]
E -->|no| H[等待 / 重试]
3. 应用自身也要有重试机制 即使 Compose 做了健康检查,应用也不应该假设依赖永远稳定。
实际生产中,MySQL、Redis 可能重启、网络可能抖动、连接池可能断开。应用侧仍然需要:
数据库连接池重试
Redis 客户端自动重连
MQ 消费重试
启动失败快速暴露日志
健康检查接口能反映真实依赖状态
Compose 是编排工具,不是保姆。它可以帮你开门,但不能替你进屋写代码。
九、Compose 网络深入 1. 默认网络 如果没有显式声明网络,Compose 会自动创建一个默认网络。
1 2 3 4 5 services: app: image: mall-app mysql: image: mysql:8.0
app 可以访问:
因为服务名 mysql 会被注册到 Docker 内部 DNS。
2. 服务名就是 DNS 名 示例:
1 2 3 4 5 services: app: image: mall-app redis: image: redis:7
应用配置应写:
1 2 SPRING_DATA_REDIS_HOST: redis SPRING_DATA_REDIS_PORT: 6379
不要写:
1 SPRING_DATA_REDIS_HOST: 127.0 .0 .1
因为在容器里,127.0.0.1 指的是 app 容器自己,不是 Redis 容器,也不是宿主机。
3. 自定义网络 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 services: nginx: image: nginx networks: - frontend app: image: mall-app networks: - frontend - backend mysql: image: mysql:8.0 networks: - backend networks: frontend: backend:
网络隔离图:
flowchart LR
subgraph frontend[frontend 网络]
N[Nginx]
A[App]
end
subgraph backend[backend 网络]
A2[App]
M[MySQL]
R[Redis]
end
N --> A
A2 --> M
A2 --> R
这样:
Nginx 能访问 App
App 能访问 MySQL 和 Redis
Nginx 不能直接访问 MySQL
4. aliases 网络别名 1 2 3 4 5 6 7 8 9 10 11 services: mysql: image: mysql:8.0 networks: backend: aliases: - db - mysql-master networks: backend:
同网络下可以通过这些名字访问:
1 2 3 mysql:3306 db:3306 mysql-master:3306
5. internal 内部网络 1 2 3 networks: backend: internal: true
internal: true 表示该网络是内部网络,通常用于加强隔离。
6. external 外部网络 如果网络已经提前创建:
1 docker network create shared-net
Compose 中可以引用:
1 2 3 4 5 6 7 8 9 services: app: image: mall-app networks: - shared-net networks: shared-net: external: true
适合多个 Compose 项目共享同一个网络。
7. host 网络模式 1 2 3 4 services: app: image: mall-app network_mode: host
使用 host 网络时,容器直接使用宿主机网络。此时 ports 映射通常不再有意义。
不建议默认使用 host 网络,除非你明确知道自己要绕开容器网络隔离。
十、端口映射深入 1. ports 基本语法
含义:
也就是:
2. 指定协议 1 2 3 ports: - "8080:8080/tcp" - "5353:5353/udp"
3. 只绑定本机 1 2 ports: - "127.0.0.1:8080:8080"
这表示只有宿主机本机能访问,外部机器不能访问。
适合:
本地开发数据库
本机调试面板
不希望公网暴露的管理服务
4. 不要把所有中间件都暴露出去 很多人本地开发习惯这样写:
1 2 3 4 5 6 7 services: mysql: ports: - "3306:3306" redis: ports: - "6379:6379"
本地可以,生产环境要谨慎。
如果只有 app 需要访问 MySQL 和 Redis,那它们根本不需要发布到宿主机:
1 2 3 4 5 6 services: mysql: image: mysql:8.0 redis: image: redis:7
App 在内部网络中通过 mysql、redis 访问即可。
5. 端口冲突排查 如果启动报错:
1 Bind for 0.0.0.0:8080 failed: port is already allocated
说明宿主机端口已被占用。
排查:
或:
1 sudo netstat -tunlp | grep 8080
修改 .env:
Compose:
1 2 ports: - "${APP_PORT:-8080}:8080"
十一、数据卷与持久化深入 1. 命名卷 1 2 3 4 5 6 7 8 services: mysql: image: mysql:8.0 volumes: - mysql-data:/var/lib/mysql volumes: mysql-data:
实际 volume 名通常会带项目名前缀:
查看:
2. 绑定挂载 1 2 3 4 5 6 services: nginx: image: nginx volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro - ./nginx/html:/usr/share/nginx/html:ro
适合:
Nginx 配置
本地开发代码挂载
本地临时日志目录
测试配置文件
3. tmpfs 1 2 3 4 5 services: app: image: mall-app tmpfs: - /tmp
适合临时文件,不需要落盘。
4. external volume 提前创建:
1 docker volume create mysql-prod-data
Compose 引用:
1 2 3 4 5 6 7 8 9 services: mysql: image: mysql:8.0 volumes: - mysql-prod-data:/var/lib/mysql volumes: mysql-prod-data: external: true
这样即使 Compose 项目名变化,也不会改变实际 volume 名。
5. 数据库备份:mysqldump 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #!/usr/bin/env bash set -e BACKUP_DIR="./backup" MYSQL_SERVICE="mysql" MYSQL_USER="root" MYSQL_PASSWORD="${MYSQL_ROOT_PASSWORD:-123456} " MYSQL_DATABASE="${MYSQL_DATABASE:-mall} " TIME=$(date +%Y%m%d_%H%M%S)mkdir -p "${BACKUP_DIR} " docker compose exec -T ${MYSQL_SERVICE} \ mysqldump -u${MYSQL_USER} -p${MYSQL_PASSWORD} \ --databases ${MYSQL_DATABASE} \ > "${BACKUP_DIR} /${MYSQL_DATABASE} _${TIME} .sql" echo "backup success: ${BACKUP_DIR} /${MYSQL_DATABASE} _${TIME} .sql"
6. 数据库恢复 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/usr/bin/env bash set -e SQL_FILE="$1 " MYSQL_SERVICE="mysql" MYSQL_USER="root" MYSQL_PASSWORD="${MYSQL_ROOT_PASSWORD:-123456} " if [ -z "${SQL_FILE} " ]; then echo "Usage: ./restore-mysql.sh <sql-file>" exit 1fi cat "${SQL_FILE} " | docker compose exec -T ${MYSQL_SERVICE} \ mysql -u${MYSQL_USER} -p${MYSQL_PASSWORD} echo "restore success: ${SQL_FILE} "
7. volume 文件级备份 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #!/usr/bin/env bash set -e VOLUME_NAME="$1 " BACKUP_DIR="./volume-backup" TIME=$(date +%Y%m%d_%H%M%S)if [ -z "${VOLUME_NAME} " ]; then echo "Usage: ./backup-volume.sh <volume-name>" exit 1fi mkdir -p "${BACKUP_DIR} " docker run --rm \ -v ${VOLUME_NAME} :/volume \ -v $(pwd )/${BACKUP_DIR} :/backup \ alpine \ tar czf /backup/${VOLUME_NAME} _${TIME} .tar.gz -C /volume .echo "backup success: ${BACKUP_DIR} /${VOLUME_NAME} _${TIME} .tar.gz"
十二、profiles:让可选服务按需启动 Profiles 可以让某些服务默认不启动,只有指定 profile 时才启动。
适合:
Adminer
phpMyAdmin
RedisInsight
Prometheus
Grafana
Jaeger
临时 debug 工具
本地 mock 服务
1. 示例:Adminer 只在 debug 模式启动 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 services: mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: mall adminer: image: adminer profiles: - debug ports: - "8081:8080" depends_on: - mysql
默认启动:
只启动 MySQL,不启动 Adminer。
带 profile 启动:
1 docker compose --profile debug up -d
2. 多 profile 1 2 3 4 5 6 services: grafana: image: grafana/grafana profiles: - monitor - debug
启动:
1 docker compose --profile monitor up -d
3. profiles 设计建议 建议把核心服务默认启用,可选工具放 profile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 services: app: image: mall-app mysql: image: mysql:8.0 redis: image: redis:7 adminer: image: adminer profiles: - debug
不要把核心服务放 profile,否则别人直接 docker compose up -d 时可能启动不完整。
十三、多 Compose 文件与环境覆盖 实际项目通常有多套环境:
不建议复制四份完整 Compose 文件。更好的做法是:
1 2 3 4 compose.yaml # 基础配置 compose.dev.yaml # 开发环境覆盖 compose.test.yaml # 测试环境覆盖 compose.prod.yaml # 生产环境覆盖
1. 基础配置 compose.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 services: app: image: registry.example.com/mall-app:${APP_VERSION:?APP_VERSION required} environment: SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-dev} SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/${MYSQL_DATABASE:-mall}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD required} SPRING_DATA_REDIS_HOST: redis depends_on: mysql: condition: service_healthy redis: condition: service_healthy mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD required} MYSQL_DATABASE: ${MYSQL_DATABASE:-mall} volumes: - mysql-data:/var/lib/mysql healthcheck: test: ["CMD" , "mysqladmin" , "ping" , "-h" , "localhost" , "-uroot" , "-p${MYSQL_ROOT_PASSWORD}" ] interval: 5s timeout: 3s retries: 20 start_period: 30s redis: image: redis:7 command: ["redis-server" , "--appendonly" , "yes" ] volumes: - redis-data:/data healthcheck: test: ["CMD" , "redis-cli" , "ping" ] interval: 5s timeout: 3s retries: 20 volumes: mysql-data: redis-data:
2. 开发环境覆盖 compose.dev.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 services: app: build: context: . dockerfile: Dockerfile ports: - "8080:8080" volumes: - ./logs:/app/logs mysql: ports: - "3306:3306" redis: ports: - "6379:6379"
3. 生产环境覆盖 compose.prod.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 services: app: restart: unless-stopped ports: - "127.0.0.1:8080:8080" logging: driver: json-file options: max-size: "100m" max-file: "3" mem_limit: 768m cpus: 1.0 mysql: restart: unless-stopped logging: driver: json-file options: max-size: "100m" max-file: "3" redis: restart: unless-stopped logging: driver: json-file options: max-size: "100m" max-file: "3"
4. 启动不同环境 开发:
1 docker compose -f compose.yaml -f compose.dev.yaml up -d --build
生产:
1 docker compose -f compose.yaml -f compose.prod.yaml up -d
查看最终配置:
1 docker compose -f compose.yaml -f compose.prod.yaml config
5. 多文件合并规则理解 Compose 会按 -f 指定顺序合并文件,后面的文件会覆盖或追加前面的配置。
flowchart LR
A[compose.yaml 基础配置] --> C[最终配置]
B[compose.prod.yaml 生产覆盖] --> C
C --> D[docker compose up -d]
一般原则:
基础文件写所有环境共用内容。
dev 文件只写开发环境差异。
prod 文件只写生产环境差异。
不要复制粘贴整份配置,否则后期维护很痛苦。
十四、完整实战:Spring Boot + MySQL + Redis + Nginx 1. 项目目录结构 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 mall-compose/ ├── compose.yaml ├── compose.dev.yaml ├── compose.prod.yaml ├── .env.example ├── Dockerfile ├── nginx/ │ └── conf.d/ │ └── mall.conf ├── scripts/ │ ├── deploy.sh │ ├── restart-app.sh │ ├── backup-mysql.sh │ ├── restore-mysql.sh │ ├── logs.sh │ └── health.sh ├── src/ ├── pom.xml └── target/
2. Spring Boot Dockerfile 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 FROM maven:3.9 -eclipse-temurin-17 AS builderWORKDIR /build COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests FROM eclipse-temurin:17 -jre AS runnerRUN groupadd -r app && useradd -r -g app app WORKDIR /app COPY --from=builder /build/target/*.jar app.jar RUN chown -R app:app /app USER appEXPOSE 8080 ENV JAVA_OPTS="-Xms256m -Xmx512m" ENTRYPOINT ["sh" , "-c" , "java $JAVA_OPTS -jar app.jar" ]
3. .env.example 1 2 3 4 5 6 7 PROJECT_NAME=mall-dev APP_VERSION=1.0.0 APP_PORT=8080 NGINX_PORT=80 MYSQL_ROOT_PASSWORD=please-change-me MYSQL_DATABASE=mall SPRING_PROFILES_ACTIVE=dev
使用时复制:
4. compose.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 name: ${PROJECT_NAME:-mall-dev} services: nginx: image: nginx:1.27-alpine depends_on: app: condition: service_healthy volumes: - ./nginx/conf.d:/etc/nginx/conf.d:ro networks: - frontend restart: unless-stopped app: image: mall-app:${APP_VERSION:?APP_VERSION required} build: context: . dockerfile: Dockerfile target: runner environment: SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE:-dev} JAVA_OPTS: "-Xms256m -Xmx512m" SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/${MYSQL_DATABASE:-mall}?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai SPRING_DATASOURCE_USERNAME: root SPRING_DATASOURCE_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD required} SPRING_DATA_REDIS_HOST: redis SPRING_DATA_REDIS_PORT: 6379 depends_on: mysql: condition: service_healthy redis: condition: service_healthy networks: - frontend - backend healthcheck: test: ["CMD-SHELL" , "wget -qO- http://localhost:8080/actuator/health | grep UP || exit 1" ] interval: 10s timeout: 3s retries: 10 start_period: 30s restart: unless-stopped mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD required} MYSQL_DATABASE: ${MYSQL_DATABASE:-mall} TZ: Asia/Shanghai command: - --character-set-server=utf8mb4 - --collation-server=utf8mb4_unicode_ci - --default-time-zone=+08:00 volumes: - mysql-data:/var/lib/mysql networks: - backend healthcheck: test: ["CMD-SHELL" , "mysqladmin ping -h localhost -uroot -p${MYSQL_ROOT_PASSWORD} || exit 1" ] interval: 5s timeout: 3s retries: 20 start_period: 30s restart: unless-stopped redis: image: redis:7-alpine command: ["redis-server" , "--appendonly" , "yes" ] volumes: - redis-data:/data networks: - backend healthcheck: test: ["CMD" , "redis-cli" , "ping" ] interval: 5s timeout: 3s retries: 20 restart: unless-stopped adminer: image: adminer:latest profiles: - debug ports: - "8081:8080" depends_on: mysql: condition: service_healthy networks: - backend networks: frontend: backend: internal: false volumes: mysql-data: redis-data:
5. compose.dev.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 services: nginx: ports: - "${NGINX_PORT:-80}:80" app: ports: - "${APP_PORT:-8080}:8080" volumes: - ./logs:/app/logs mysql: ports: - "3306:3306" redis: ports: - "6379:6379"
6. compose.prod.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 services: nginx: ports: - "${NGINX_PORT:-80}:80" logging: driver: json-file options: max-size: "100m" max-file: "3" app: build: null image: registry.example.com/mall/mall-app:${APP_VERSION:?APP_VERSION required} mem_limit: 768m cpus: 1.0 logging: driver: json-file options: max-size: "100m" max-file: "3" mysql: ports: [] logging: driver: json-file options: max-size: "100m" max-file: "3" redis: ports: [] logging: driver: json-file options: max-size: "100m" max-file: "3"
说明:
开发环境可以暴露 MySQL 和 Redis,方便本机工具连接。
生产环境不暴露 MySQL 和 Redis,只让 app 通过内部网络访问。
生产环境使用远程镜像仓库的镜像,不在服务器上临时构建。
7. Nginx 配置 nginx/conf.d/mall.conf:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 server { listen 80 ; server_name _; location / { proxy_pass http://app:8080; proxy_http_version 1 .1 ; 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 /actuator/health { proxy_pass http://app:8080/actuator/health; proxy_http_version 1 .1 ; proxy_set_header Host $host ; } }
Nginx 访问 app 用的是:
因为它们在同一个 frontend 网络里。
8. 启动开发环境 1 2 3 cp .env.example .env docker compose -f compose.yaml -f compose.dev.yaml up -d --build
启动 debug 工具:
1 docker compose -f compose.yaml -f compose.dev.yaml --profile debug up -d
访问:
1 2 curl http://localhost:8080/actuator/health curl http://localhost
9. 启动生产环境 1 2 3 docker compose -f compose.yaml -f compose.prod.yaml pull docker compose -f compose.yaml -f compose.prod.yaml up -d
更新 app:
1 2 3 docker compose -f compose.yaml -f compose.prod.yaml pull app docker compose -f compose.yaml -f compose.prod.yaml up -d --no-deps app
十五、生产部署脚本 1. deploy.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #!/usr/bin/env bash set -euo pipefail COMPOSE_FILES="-f compose.yaml -f compose.prod.yaml" APP_VERSION="${1:-} " if [ -z "${APP_VERSION} " ]; then echo "Usage: ./scripts/deploy.sh <app-version>" exit 1fi export APP_VERSION="${APP_VERSION} " echo "==> Checking final compose config" docker compose ${COMPOSE_FILES} config > /tmp/compose.final.yamlecho "==> Pulling images" docker compose ${COMPOSE_FILES} pullecho "==> Starting services" docker compose ${COMPOSE_FILES} up -decho "==> Service status" docker compose ${COMPOSE_FILES} psecho "==> Recent app logs" docker compose ${COMPOSE_FILES} logs --tail =100 app
使用:
1 2 chmod +x scripts/deploy.sh ./scripts/deploy.sh 1.0.3
2. restart-app.sh 1 2 3 4 5 6 7 #!/usr/bin/env bash set -euo pipefail COMPOSE_FILES="-f compose.yaml -f compose.prod.yaml" docker compose ${COMPOSE_FILES} restart app docker compose ${COMPOSE_FILES} logs --tail =100 -f app
3. logs.sh 1 2 3 4 5 6 7 #!/usr/bin/env bash set -euo pipefail SERVICE="${1:-app} " TAIL="${2:-200} " docker compose -f compose.yaml -f compose.prod.yaml logs --tail ="${TAIL} " -f "${SERVICE} "
使用:
1 2 ./scripts/logs.sh app 300 ./scripts/logs.sh mysql 100
4. health.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/usr/bin/env bash set -euo pipefail COMPOSE_FILES="-f compose.yaml -f compose.prod.yaml" echo "==> Compose services" docker compose ${COMPOSE_FILES} psecho "==> App health" curl -fsS http://127.0.0.1:8080/actuator/health || true echo echo "==> Docker stats snapshot" docker stats --no-stream
5. backup-mysql.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #!/usr/bin/env bash set -euo pipefail COMPOSE_FILES="-f compose.yaml -f compose.prod.yaml" BACKUP_DIR="./backup/mysql" TIME=$(date +%Y%m%d_%H%M%S) MYSQL_DATABASE="${MYSQL_DATABASE:-mall} " MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD required} " mkdir -p "${BACKUP_DIR} " docker compose ${COMPOSE_FILES} exec -T mysql \ mysqldump -uroot -p"${MYSQL_ROOT_PASSWORD} " \ --single-transaction \ --routines \ --triggers \ --databases "${MYSQL_DATABASE} " \ > "${BACKUP_DIR} /${MYSQL_DATABASE} _${TIME} .sql" gzip "${BACKUP_DIR} /${MYSQL_DATABASE} _${TIME} .sql" echo "backup success: ${BACKUP_DIR} /${MYSQL_DATABASE} _${TIME} .sql.gz"
6. rollback.sh 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #!/usr/bin/env bash set -euo pipefail COMPOSE_FILES="-f compose.yaml -f compose.prod.yaml" ROLLBACK_VERSION="${1:-} " if [ -z "${ROLLBACK_VERSION} " ]; then echo "Usage: ./scripts/rollback.sh <app-version>" exit 1fi export APP_VERSION="${ROLLBACK_VERSION} " echo "==> Rolling back to ${APP_VERSION} " docker compose ${COMPOSE_FILES} pull app docker compose ${COMPOSE_FILES} up -d --no-deps app docker compose ${COMPOSE_FILES} logs --tail =100 app
十六、CI 中使用 Compose Compose 很适合在 CI 中启动依赖服务,跑集成测试。
1. CI 测试环境 compose.test.yaml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 services: app: build: context: . dockerfile: Dockerfile environment: SPRING_PROFILES_ACTIVE: test depends_on: mysql: condition: service_healthy redis: condition: service_healthy mysql: image: mysql:8.0 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_DATABASE: mall_test healthcheck: test: ["CMD" , "mysqladmin" , "ping" , "-h" , "localhost" , "-uroot" , "-p123456" ] interval: 5s timeout: 3s retries: 20 redis: image: redis:7-alpine healthcheck: test: ["CMD" , "redis-cli" , "ping" ] interval: 5s timeout: 3s retries: 20
2. CI 脚本示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/usr/bin/env bash set -euo pipefail docker compose -f compose.test.yaml up -d --buildsleep 10 docker compose -f compose.test.yaml ps mvn test docker compose -f compose.test.yaml down -v
如果测试失败,建议保留日志:
1 docker compose -f compose.test.yaml logs > compose-test.log
十七、常见问题与排错 1. 变量没有生效 排查:
检查:
.env 是否在 compose.yaml 同目录?
变量名是否写错?
Shell 环境变量是否覆盖了 .env?
是否把 env_file 误当成 Compose 插值来源?
2. app 连不上 mysql 检查服务名:
1 SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/mall
不要写:
1 jdbc:mysql://127.0.0.1:3306/mall
进入 app 容器测试:
1 docker compose exec app sh
如果有 nc:
查看 MySQL 日志:
1 docker compose logs -f mysql
3. depends_on 写了还是失败 原因:短语法不保证服务 ready。
使用:
1 2 3 depends_on: mysql: condition: service_healthy
并给 mysql 配置 healthcheck。
4. 端口被占用
改 .env:
5. 数据库数据丢了 检查是否使用了命名卷:
查看服务挂载:
如果你执行过:
那卷可能已经被删除。这个命令对数据库杀伤力很大,别把它当普通 stop 用。
6. 修改 compose.yaml 后没有生效 重新创建容器:
1 docker compose up -d --force-recreate
如果改了镜像构建内容:
1 docker compose up -d --build
如果只更新 app:
1 docker compose up -d --no-deps --build app
7. YAML 格式错误 YAML 对缩进很敏感。
错误:
1 2 3 services: app: image: nginx
建议统一两个空格:
1 2 3 services: app: image: nginx
校验:
8. profile 服务没有启动 如果服务写了:
默认不会启动。
需要:
1 docker compose --profile debug up -d
9. container_name 导致冲突 如果写死:
另一套项目也写 container_name: mysql,就会冲突。
建议删除 container_name,让 Compose 自动命名。
10. Nginx 502 排查顺序:
1 2 3 4 5 6 7 8 9 10 11 12 docker compose ps docker compose logs -f app docker compose logs -f nginx docker compose exec nginx sh wget -qO- http://app:8080/actuator/health
常见原因:
Nginx 和 app 不在同一个网络
app 服务名写错
app 端口写错
app 健康检查没过
Spring Boot 绑定了错误地址
十八、Compose 生产实践清单 1. 镜像版本要明确 不要:
推荐:
1 image: registry.example.com/mall/mall-app:1.0.3
或者:
1 image: registry.example.com/mall/mall-app:${APP_VERSION:?APP_VERSION required}
2. 生产环境不要挂载源码 开发环境可以:
生产环境不建议。生产环境应该使用构建好的镜像,保证不可变部署。
3. 数据必须持久化 MySQL:
1 2 volumes: - mysql-data:/var/lib/mysql
Redis AOF:
1 2 3 command: ["redis-server" , "--appendonly" , "yes" ]volumes: - redis-data:/data
4. 不暴露不必要的端口 生产环境中,MySQL、Redis 通常不需要:
内部服务通过 Compose 网络访问即可。
5. 加 restart 策略
6. 加健康检查 关键服务都建议配置:
1 2 3 4 5 healthcheck: test: ["CMD" , "redis-cli" , "ping" ] interval: 5s timeout: 3s retries: 20
7. 控制日志大小 1 2 3 4 5 logging: driver: json-file options: max-size: "100m" max-file: "3"
8. 设置资源限制 1 2 mem_limit: 768m cpus: 1.0
9. 配置备份脚本 至少要有:
MySQL dump 备份
volume 备份
备份文件压缩
定期清理旧备份
恢复演练
10. 发布前看最终配置 1 docker compose -f compose.yaml -f compose.prod.yaml config
这是一个非常简单但非常救命的习惯。
十九、Compose 与 Kubernetes 的边界 Compose 适合单机编排,Kubernetes 适合集群编排。
能力
Docker Compose
Kubernetes
本地开发
很适合
偏重
单机部署
适合
可以但复杂
多机调度
不擅长
擅长
自动扩缩容
不擅长
擅长
服务发现
单机 DNS
集群 DNS
滚动发布
基础能力弱
成熟
故障迁移
弱
强
学习成本
低
高
一句话:
Compose 适合把一组容器在一台机器上优雅地跑起来;Kubernetes 适合把一组服务在一个集群里长期治理起来。
不要因为 K8s 高级就什么都上 K8s。很多内部小系统,一份 Compose 加几个脚本就够了。技术选型不是比谁更重,而是比谁更合适。
二十、命令速查表 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 docker compose version docker compose up -d docker compose up -d --build docker compose stop docker compose start docker compose restart docker compose down docker compose down -v docker compose ps docker compose logs -f docker compose logs -f app docker compose exec app sh docker compose run --rm app sh docker compose build docker compose pull docker compose push docker compose config docker compose -p mall-dev up -d docker compose -f compose.yaml -f compose.prod.yaml up -d docker compose --profile debug up -d docker compose up -d --scale app=3
总结 Docker Compose 的核心能力不是“帮你少敲几个命令”,而是把多容器应用变成一份可读、可版本管理、可复现的声明式配置。
你需要真正掌握的不是某一个字段,而是这套模型:
Project:一组 Compose 资源的命名空间
Service:服务声明
Container:服务运行实例
Network:服务间通信边界
Volume:持久化数据
Environment:配置注入
Healthcheck:可用性判断
Depends_on:依赖顺序控制
Profiles:可选服务开关
Override files:多环境配置覆盖
实际项目里,建议形成这样的习惯:
使用现代 docker compose,不要继续依赖老的 docker-compose。
新项目省略顶层 version。
使用 compose.yaml 作为基础配置。
使用 compose.dev.yaml、compose.prod.yaml 做环境覆盖。
使用 .env.example 给团队提供配置模板。
使用服务名进行容器间通信,不要在容器里写 127.0.0.1 访问别的服务。
数据库必须挂载命名卷。
生产环境不要暴露 MySQL、Redis 等内部端口。
核心服务配置 healthcheck 和 restart。
发布前执行 docker compose config 看最终配置。
日志必须限制大小。
数据必须有备份和恢复脚本。
Compose 写得好,开发环境和测试环境会非常顺滑;Compose 写得差,也能把一个小项目编排成“分布式精神内耗现场”。所以别只追求能跑,要追求可维护、可迁移、可排错、可恢复。
参考资料
Docker Docs:Docker Compose
Docker Docs:Compose file reference
Docker Docs:Services top-level element
Docker Docs:Networks top-level element
Docker Docs:Volumes top-level element
Docker Docs:Interpolation
Docker Docs:Environment variables in Compose
Docker Docs:Control startup and shutdown order in Compose
Docker Docs:Use profiles in Docker Compose
Docker Docs:Use multiple Compose files
Docker Docs:Merge Compose files
Docker Docs:Use Compose in production
Docker Docs:docker compose CLI reference