news 2026/4/28 3:43:24

Docker 部署 MySQL,这 10 个坑你不踩才怪(Docker 部署篇)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Docker 部署 MySQL,这 10 个坑你不踩才怪(Docker 部署篇)

公关众注号:IT安装手册

MySQL 避坑指南系列·第②篇,共 4 篇。
上一篇讲了安装配置的 10 个坑(字符集、配置文件、用户权限),本篇聚焦 Docker 部署——这是整个系列踩坑最密集的环节。


用 Docker 部署 MySQL,从写 Compose 文件到跑起来,中间有 10 个高频坑在等着你。它们分布在 YAML 语法、依赖控制、环境变量管理、网络与卷配置、多环境切换、健康检查 6 个方向,每个坑都能让容器跑不起来或者数据莫名其妙消失。


环境说明

项目版本
MySQL8.0.36(Docker 官方镜像)
Docker24.x+
Docker Composev2.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 refusedCommunications 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

推荐用命名 Volumemysql_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 新建文件默认权限通常是644664,但如果 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 查询怎么找。这些问题在开发时感觉不到,数据量一上去就是直接的性能事故。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/28 3:42:16

百度网盘秒传脚本:3步轻松实现文件永久分享的完整指南

百度网盘秒传脚本:3步轻松实现文件永久分享的完整指南 【免费下载链接】rapid-upload-userscript-doc 秒传链接提取脚本 - 文档&教程 项目地址: https://gitcode.com/gh_mirrors/ra/rapid-upload-userscript-doc 你是否曾为百度网盘分享链接频繁失效而烦…

作者头像 李华
网站建设 2026/4/28 3:31:26

App-Agent:基于视觉感知与LLM的智能体应用自动化实战

1. 项目概述:从“App-Agent”看智能体驱动的应用自动化新范式最近在开源社区里,一个名为ngo275/app-agent的项目引起了我的注意。乍一看,这只是一个GitHub仓库名,但当你深入其README和代码结构,你会发现它指向了一个极…

作者头像 李华
网站建设 2026/4/28 3:28:31

Transformer残差流与内部策略的深度解析

1. Transformer残差流与内部策略的深层解析在深入探讨大语言模型(LLM)的内部工作机制前,我们需要理解Transformer架构中一个关键但常被忽视的组件——残差流(residual stream)。这个信息高速公路贯穿整个模型,承载着从输入到输出的语义演变过程。1.1 残差…

作者头像 李华
网站建设 2026/4/28 3:13:50

俄罗斯BITBLAZE Titan BM15 Arm Linux笔记本评测

1. 俄罗斯BITBLAZE Titan BM15 Arm Linux笔记本深度解析最近俄罗斯科技公司Prombit推出了一款名为BITBLAZE Titan BM15的Arm架构Linux笔记本,搭载了Baikal-M1八核处理器。作为一名长期关注Arm生态的开发者,这款产品引起了我的浓厚兴趣。不同于市面上常见…

作者头像 李华