公关众注号:IT安装手册
MySQL 避坑指南系列·第②篇,共 4 篇。
上一篇讲了安装配置的 10 个坑(字符集、配置文件、用户权限),本篇聚焦 Docker 部署——这是整个系列踩坑最密集的环节。
用 Docker 部署 MySQL,从写 Compose 文件到跑起来,中间有 10 个高频坑在等着你。它们分布在 YAML 语法、依赖控制、环境变量管理、网络与卷配置、多环境切换、健康检查 6 个方向,每个坑都能让容器跑不起来或者数据莫名其妙消失。
环境说明
| 项目 | 版本 |
|---|---|
| MySQL | 8.0.36(Docker 官方镜像) |
| Docker | 24.x+ |
| Docker Compose | v2.x(命令是docker compose,不是docker-compose) |
一、YAML 语法坑(2 个)
坑 1:缩进用了 Tab,Compose 启动报神秘错误
现象:
yaml: line 8: found character that cannot start any token明明格式看起来没问题,一跑就报错。
根本原因:YAML 规范只允许空格缩进,Tab 是非法字符。很多编辑器默认插入 Tab,肉眼看不出区别,但解析器一跑就炸。
解决方案:编辑器统一配置为 2 空格缩进,并用yamllint做静态检查:
pip install yamllint yamllint docker-compose.yml标准 Compose 缩进结构(每层 2 个空格):
services: # 0 空格 mysql: # 2 空格 image: mysql:8.0.36 # 4 空格 environment: # 4 空格 MYSQL_ROOT_PASSWORD: "root" # 6 空格坑 2:密码是纯数字或 yes/no,值被自动转类型
现象:密码设了123456,连接时死活认证失败;或者某个配置项写了yes,实际被解析成布尔值true。
根本原因:YAML 会把yes/no/true/false/on/off解析为布尔值,纯数字解析为整数,传给容器的值和你写的不一样。
解决方案:环境变量的值统一加双引号:
# ❌ 有隐患 environment: MYSQL_ROOT_PASSWORD: 123456 MYSQL_ENABLE_LOG: yes # ✅ 统一加引号,所见即所得 environment: MYSQL_ROOT_PASSWORD: "123456" MYSQL_ENABLE_LOG: "yes"二、依赖控制坑(1 个)
坑 3:应用容器比 MySQL 先就绪,启动就报连接失败
现象:docker compose up后应用容器立刻报Connection refused或Communications link failure,手动重启应用容器就好了。
根本原因:depends_on只等 MySQL 容器进程启动,不等 MySQL真正可以接受连接。MySQL 首次启动要初始化数据目录,通常需要 10~30 秒,这段时间 MySQL 进程在运行,但还不接受连接。
解决方案:结合healthcheck+condition: service_healthy:
services: mysql: image: mysql:8.0.36 environment: MYSQL_ROOT_PASSWORD: "${MYSQL_ROOT_PASSWORD}" healthcheck: test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] interval: 10s # 每 10 秒检查一次 timeout: 5s # 超时 5 秒算本次失败 retries: 5 # 连续失败 5 次才标记为 unhealthy start_period: 30s # 启动后 30 秒内不计入失败(初始化宽限期) app: image: your-app:latest depends_on: mysql: condition: service_healthy # 等 MySQL 变为 healthy 才启动应用层也要有自己的重连逻辑(指数退避),不能只靠 Compose 依赖兜底。Compose 管的是启动顺序,管不了运行中 MySQL 重启后应用的自动重连。
三、环境变量管理坑(2 个)
坑 4:密码硬编码在 `docker-compose.yml` 里,提交到 Git 就泄漏了
现象:docker-compose.yml里直接写了MYSQL_ROOT_PASSWORD: mypassword,推到 GitHub 后收到安全告警,或者密码直接暴露在代码仓库历史记录里(即使后来删了,历史 commit 里还有)。
根本原因:配置和代码没有分离,敏感信息混入版本控制。
解决方案:用.env文件管理敏感变量,.env加入.gitignore不提交:
# .env(不提交 Git!) MYSQL_ROOT_PASSWORD=SuperSecret123! MYSQL_DATABASE=myapp MYSQL_USER=appuser MYSQL_PASSWORD=AppPassword456!# docker-compose.yml 只引用变量名,不写值 services: mysql: image: mysql:8.0.36 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} MYSQL_USER: ${MYSQL_USER} MYSQL_PASSWORD: ${MYSQL_PASSWORD}# .gitignore 必须加这几行 .env .env.* !.env.example# .env.example 提交 Git,作为团队模板(值用占位符) MYSQL_ROOT_PASSWORD=请替换为强密码 MYSQL_DATABASE=myapp MYSQL_USER=appuser MYSQL_PASSWORD=请替换为强密码坑 5:改了 `MYSQL_ROOT_PASSWORD`,密码还是没变
现象:在.env里改了密码,docker compose up -d后用新密码连不上,用旧密码反而能连。
根本原因:MySQL Docker 镜像只在首次初始化(数据目录为空)时读取环境变量建库建账号。只要/var/lib/mysql目录里有数据,环境变量就会被完全忽略。
解决方案:
# 场景一:开发环境,数据可以丢,清干净重来 docker compose down -v # ⚠️ -v 会同时删 Volume,数据清空 docker compose up -d # 重新初始化,新密码生效 # 场景二:数据不能丢,进容器手动改密码 docker exec -it mysql容器名 mysql -uroot -p旧密码ALTER USER 'root'@'localhost' IDENTIFIED BY '新密码'; FLUSH PRIVILEGES;四、网络与卷配置坑(3 个)
坑 6:应用容器用 `localhost` 连 MySQL,互相找不到
现象:应用配置文件里写的是DB_HOST=localhost,容器启动后报Connection refused,明明 MySQL 容器跑得好好的。
根本原因:每个容器有独立的网络命名空间,localhost指的是容器自身,而不是宿主机或其他容器。
解决方案:同一 Compose 项目内,用服务名作为 host:
services: mysql: image: mysql:8.0.36 # 服务名 "mysql" 自动成为容器的 hostname app: image: your-app environment: DB_HOST: mysql # ✅ 服务名,不是 localhost DB_PORT: "3306"跨 Compose 项目通信时,用 external network:
# 先创建共享网络 docker network create shared_net# 两个 Compose 文件都声明使用同一个 external network services: mysql: networks: - shared_net networks: shared_net: external: true坑 7:没挂 Volume,`docker compose down` 后数据全没了
现象:重新启动容器后,之前创建的数据库、表、数据全部消失,还以为是 bug。
根本原因:容器是无状态的。没有 Volume 的情况下,数据写在容器可写层,down删容器数据跟着消失。
解决方案:必须挂载 Volume 持久化 MySQL 数据目录:
services: mysql: image: mysql:8.0.36 volumes: - mysql_data:/var/lib/mysql # 必须:数据持久化 - ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro # 可选:初始化 SQL - ./my.cnf:/etc/mysql/conf.d/custom.cnf:ro # 可选:自定义配置 volumes: mysql_data: # 声明命名 Volume,由 Docker 管理 driver: local推荐用命名 Volume(
mysql_data:),而不是绑定挂载(./data:/var/lib/mysql)。命名 Volume 在 Docker Desktop(Windows/Mac)上性能更好,且不会遇到宿主机文件权限问题。
坑 8:挂载 `my.cnf` 后 MySQL 拒绝加载配置
现象:挂载了自定义配置文件,MySQL 日志里出现:
[Warning] World-writable config file '/etc/mysql/conf.d/custom.cnf' is ignored!根本原因:MySQL 出于安全考虑,拒绝加载others可写(权限包含o+w)的配置文件。Linux 新建文件默认权限通常是644或664,但如果 umask 设置不当,可能创建出666甚至777的文件。
解决方案:
# 在宿主机上修正配置文件权限 chmod 644 ./my.cnf # 验证权限 ls -la my.cnf # -rw-r--r-- 1 user group ... my.cnf ← 这样就对了五、多环境切换坑(1 个)
坑 9:开发和生产共用一个 Compose 文件,改一个影响另一个
现象:本地开发时需要暴露端口方便调试,生产不能暴露;密码、资源限制、日志级别各不相同,每次部署都要手动改文件,改漏了就出事。
根本原因:没有按环境做配置分层,用一个文件通吃所有场景。
解决方案:Compose Override 文件分层管理:
项目目录/ ├── docker-compose.yml # 基础配置(公共部分,提交 Git) ├── docker-compose.dev.yml # 开发覆盖(提交 Git) ├── docker-compose.prod.yml # 生产覆盖(提交 Git) ├── .env.dev # 开发变量(不提交) └── .env.prod # 生产变量(不提交)# docker-compose.yml(基础,只写公共部分) services: mysql: image: mysql:8.0.36 environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: ${MYSQL_DATABASE} volumes: - mysql_data:/var/lib/mysql volumes: mysql_data:# docker-compose.dev.yml(开发环境差异) services: mysql: ports: - "3306:3306" # 开发暴露端口,方便本地客户端连接# docker-compose.prod.yml(生产环境差异) services: mysql: # 生产不暴露端口到宿主机 restart: unless-stopped deploy: resources: limits: cpus: "2.0" memory: 4G# 开发启动 docker compose -f docker-compose.yml -f docker-compose.dev.yml \ --env-file .env.dev up -d # 生产启动 docker compose -f docker-compose.yml -f docker-compose.prod.yml \ --env-file .env.prod up -d六、健康检查坑(1 个)
坑 10:健康检查配置了,容器一直 `unhealthy`
现象:docker ps显示容器状态是(unhealthy),但手动进容器执行 MySQL 命令完全正常。
根本原因:健康检查命令写法有问题——密码带特殊字符转义失败、-p和密码之间多了空格、或者检查命令本身的退出码不对。
解决方案:几种经过验证的写法:
healthcheck: # 写法一:最简单,不带密码(适合开发环境) test: ["CMD", "mysqladmin", "ping", "-h", "localhost"] # 写法二:带密码(-u 和 -p 与参数之间无空格) test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${MYSQL_ROOT_PASSWORD}"] # 写法三:直接执行 SQL 验证(最可靠) test: ["CMD-SHELL", "mysql -uroot -p${MYSQL_ROOT_PASSWORD} -e 'SELECT 1' || exit 1"] interval: 10s timeout: 5s retries: 5 start_period: 30s # 给初始化足够的宽限时间调试技巧——查看健康检查的执行日志:
docker inspect --format='{{json .State.Health}}' 容器名 \ | python3 -m json.tool # 输出里 Log 字段会显示每次检查命令的输出和退出码快速自检清单(Docker 部署)
启动容器前逐项确认:
xYAML 缩进全是空格,已用yamllint验证
x环境变量值已加引号,无 YAML 类型转换风险
x密码通过.env管理,未硬编码在docker-compose.yml
x.env已加入.gitignore
x已挂载命名 Volume 持久化/var/lib/mysql
x应用依赖使用condition: service_healthy
x健康检查配置正确(docker ps显示healthy)
x应用容器的DB_HOST是服务名,而不是localhost
x挂载的my.cnf权限是644
x生产环境已设置restart: unless-stopped和资源限制
小结
Docker 部署 MySQL 的坑,本质上都源于两个认知误区:① 把容器当虚拟机用(网络、存储模型完全不同);② 把开发环境的习惯带到生产(密码硬编码、没有资源限制)。把上面 10 个坑记住,Docker 部署 MySQL 就能绕开 90% 的问题。
下一篇讲 SQL 层面的坑——全表扫描怎么定位、事务没关怎么锁表、N+1 查询怎么找。这些问题在开发时感觉不到,数据量一上去就是直接的性能事故。