为什么用 Compose
本地开发时,MySQL、Redis、RabbitMQ 一个个装、一个个配,换台机器就要重来一遍;团队新人也经常卡在“我的 Redis 连不上”。Docker Compose 用一个 YAML 把应用和依赖编排在一起,docker compose up -d 就能拉起整套环境,服务名直接当主机名用,数据卷负责持久化。
它不适合大规模生产编排,Kubernetes 才是那套工具。但对单机部署、测试环境、演示环境,Compose 的性价比很高。下面以 Spring Boot 3.2 + MySQL 8.0 + Redis 7 为例,搭一套可以直接跑的环境。
compose.yaml
name: order-stack
services:
mysql:
image: mysql:8.0.36
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: order_db
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_0900_ai_ci
healthcheck:
test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p$$MYSQL_ROOT_PASSWORD --silent"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
redis:
image: redis:7.2-alpine
command: ["redis-server", "--appendonly", "yes", "--requirepass", "redis123"]
ports:
- "6379:6379"
volumes:
- redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "-a", "redis123", "ping"]
interval: 10s
timeout: 3s
retries: 10
start_period: 10s
app:
build:
context: .
dockerfile: Dockerfile
depends_on:
mysql:
condition: service_healthy
redis:
condition: service_healthy
environment:
SPRING_PROFILES_ACTIVE: docker
SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/order_db?useUnicode=true&characterEncoding=utf8&useSSL=false&allowPublicKeyRetrieval=true
SPRING_DATASOURCE_USERNAME: root
SPRING_DATASOURCE_PASSWORD: root123
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: 6379
SPRING_DATA_REDIS_PASSWORD: redis123
ports:
- "8080:8080"
restart: unless-stopped
volumes:
mysql-data:
redis-data:
几个关键点。depends_on 配合 condition: service_healthy,保证 MySQL 健康检查通过后才启动应用;只写 depends_on 不带 condition,容器启动了但数据库没就绪,应用照样连不上。健康检查里的 $$ 是 Compose 的转义,表示把 $MYSQL_ROOT_PASSWORD 留给容器内 shell 展开,写成单个 $ 会被 Compose 提前替换。
应用镜像用多阶段构建,运行阶段只保留 JRE:
FROM maven:3.9-eclipse-temurin-17 AS build
WORKDIR /build
COPY pom.xml .
RUN mvn -q dependency:go-offline
COPY src ./src
RUN mvn -q -DskipTests package
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY --from=build /build/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75", "-jar", "/app/app.jar"]
启动与验证
docker compose config
docker compose up -d --build
docker compose ps
docker compose logs -f app
curl -s http://localhost:8080/actuator/health
docker compose config 会先校验 YAML 和环境变量插值,能提前发现缩进、变量名写错。ps 里看到三个服务都是 running 且 MySQL、Redis 是 healthy,再去看应用日志。第一次启动建议带上 --build,避免代码改了镜像没更新;CI 环境可以用 --pull always 确保基础镜像不是旧的。应用启动成功后,/actuator/health 应该返回 UP,并且能看到数据库和 Redis 的检查项。
停止但保留数据用 docker compose stop;删除容器和网络、保留数据卷用 docker compose down;连数据一起清掉用 docker compose down -v。数据卷删了就找不回来,执行前确认清楚。
配置分离与网络
Compose 默认创建一个 bridge 网络,服务之间用服务名互相访问:应用连 mysql:3306、redis:6379,不能用 localhost。localhost 在容器里指向容器自己,这是新手最常见的连接失败原因。
配置通过环境变量注入,Spring Boot 的宽松绑定会把 SPRING_DATASOURCE_URL 映射到 spring.datasource.url。注意 Spring Boot 3 的 Redis 前缀是 spring.data.redis,不是 2.x 的 spring.redis,从旧项目复制配置时很容易漏改。
数据库初始化脚本放在 /docker-entrypoint-initdb.d/ 下,只在数据目录为空时执行一次。数据卷已经存在时,改 init.sql 不会重新执行,要么手动连进去执行,要么 down -v 重建。
常见坑
1. 应用比数据库先启动
只写 depends_on 不够,容器“启动”不等于“可连接”。必须配健康检查,并用 condition: service_healthy。应用侧也要有连接重试,别把启动顺序当成百分之百可靠。
2. 端口冲突
本机已经装了 MySQL 占用 3306,Compose 启动时报 port is already allocated。要么停掉本机服务,要么把宿主机端口改成 3307:3306,应用仍然连容器内的 3306。
3. 数据卷导致配置不生效
改了 MySQL 字符集或初始化 SQL,重启后没变化,因为数据目录还在。确认是不是需要迁移数据,而不是直接 down -v 删库。
4. MySQL 8 认证插件问题
旧版客户端连 MySQL 8 可能报 Public Key Retrieval is not allowed,JDBC URL 加 allowPublicKeyRetrieval=true 可以临时解决。生产环境应该配置正确的 SSL 和用户权限,不要长期依赖这个参数。
5. 时区不一致
容器默认 UTC,应用和数据库都按 UTC 存,展示时差 8 小时。MySQL 设置 TZ=Asia/Shanghai,JVM 加 -Duser.timezone=Asia/Shanghai,或者统一用 UTC 存储、展示层转换。最怕的是一部分 UTC、一部分本地时间。
6. 把密码写进镜像
environment 里的密码会出现在 docker inspect 和镜像历史里。生产环境用 Docker secrets 或外部配置中心,Compose 文件只提交模板,不提交真实密码。
排查步骤
先看容器状态和健康检查:
docker compose ps
docker inspect --format '{{json .State.Health}}' order-stack-mysql-1
docker compose logs --tail=200 mysql
docker compose exec mysql mysqladmin ping -h 127.0.0.1 -uroot -proot123
docker compose exec redis redis-cli -a redis123 ping
应用连不上数据库时,进容器验证网络和端口:
docker compose exec app sh -c "getent hosts mysql && nc -zv mysql 3306"
getent hosts 能解析出服务名,说明网络没问题;nc 不通,检查 MySQL 是否真的在监听、健康检查是否通过。最后看应用日志里的完整异常栈,不要只看最后一行。
小结
Compose 编排的重点不是把 YAML 写全,而是把启动顺序、健康检查、网络别名、数据持久化和配置注入这五件事处理对。本地环境越接近生产,联调时踩的坑越少;但生产部署仍然应该交给 Kubernetes 或专门的发布系统。
评论