公关众注号IT安装手册MySQL 避坑指南系列·第②篇共 4 篇。上一篇讲了安装配置的 10 个坑字符集、配置文件、用户权限本篇聚焦 Docker 部署——这是整个系列踩坑最密集的环节。用 Docker 部署 MySQL从写 Compose 文件到跑起来中间有 10 个高频坑在等着你。它们分布在 YAML 语法、依赖控制、环境变量管理、网络与卷配置、多环境切换、健康检查 6 个方向每个坑都能让容器跑不起来或者数据莫名其妙消失。环境说明项目版本MySQL8.0.36Docker 官方镜像Docker24.xDocker Composev2.x命令是docker compose不是docker-compose一、YAML 语法坑2 个坑 1缩进用了 TabCompose 启动报神秘错误现象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 进程在运行但还不接受连接。解决方案结合healthcheckcondition: service_healthyservices: 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_PASSWORDSuperSecret123! MYSQL_DATABASEmyapp MYSQL_USERappuser MYSQL_PASSWORDAppPassword456!# 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_DATABASEmyapp MYSQL_USERappuser 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 rootlocalhost IDENTIFIED BY 新密码; FLUSH PRIVILEGES;四、网络与卷配置坑3 个坑 6应用容器用 localhost 连 MySQL互相找不到现象应用配置文件里写的是DB_HOSTlocalhost容器启动后报Connection refused明明 MySQL 容器跑得好好的。根本原因每个容器有独立的网络命名空间localhost指的是容器自身而不是宿主机或其他容器。解决方案同一 Compose 项目内用服务名作为 hostservices: 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没挂 Volumedocker 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 DesktopWindows/Mac上性能更好且不会遇到宿主机文件权限问题。坑 8挂载 my.cnf 后 MySQL 拒绝加载配置现象挂载了自定义配置文件MySQL 日志里出现[Warning] World-writable config file /etc/mysql/conf.d/custom.cnf is ignored!根本原因MySQL 出于安全考虑拒绝加载others可写权限包含ow的配置文件。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.ymlx.env已加入.gitignorex已挂载命名 Volume 持久化/var/lib/mysqlx应用依赖使用condition: service_healthyx健康检查配置正确docker ps显示healthyx应用容器的DB_HOST是服务名而不是localhostx挂载的my.cnf权限是644x生产环境已设置restart: unless-stopped和资源限制小结Docker 部署 MySQL 的坑本质上都源于两个认知误区① 把容器当虚拟机用网络、存储模型完全不同② 把开发环境的习惯带到生产密码硬编码、没有资源限制。把上面 10 个坑记住Docker 部署 MySQL 就能绕开 90% 的问题。下一篇讲 SQL 层面的坑——全表扫描怎么定位、事务没关怎么锁表、N1 查询怎么找。这些问题在开发时感觉不到数据量一上去就是直接的性能事故。