
最近帮朋友梳理开发机上的数据库环境光清理旧 MySQL 就折腾了大半天。一台机器上 5.7 和 8.0 的残留共存rpm 装的、源码编译的、apt 装的混在一起配置互相打架启动报错查到最后居然是两个版本的初始化脚本冲突。后来我干脆把机器上所有 MySQL 清干净用 Docker 重新装了一份 MySQL 8.0并且按照“数据必须持久化”的原则做了目录挂载。这套环境稳定跑了两个月中间经历过容器重建、服务器重启、镜像升级数据一次都没丢。这篇就把完整过程写成记录从 Docker 环境准备、数据持久化原理到实际部署、验证、排错和日常维护一次讲清楚。文章适合刚接触 Docker、想用容器跑 MySQL 的开发者也适合已经在用 Docker 但担心“容器一删数据就没了”的运维朋友。我会把每一步为什么要这么做、坑在哪里都交代明白你跟着操作基本能一次跑通。1. 为什么要把MySQL装进Docker先想清楚再动手1.1 传统安装方式到底痛在哪早年装 MySQL基本逃不开 rpm、apt 或者源码编译三条路。rpm 装起来看似简单依赖关系却常常让人头大装个 mysql-community-server 要带上 mysql-community-client、mysql-community-common、mysql-community-libs 一串版本对不上就报冲突。用 apt 稍好一点但 Ubuntu 和 CentOS 的包管理策略不同软件源里的 MySQL 版本往往比较旧想装 8.0 还要额外配官方 apt 源或 rpm 源。真正麻烦的是卸载和升级。老项目里经常出现多个 MySQL 版本共存配置文件散落在 /etc/my.cnf、/etc/mysql/、/usr/my.cnf 多个位置数据目录也有 /var/lib/mysql、/opt/mysql、自定义路径之分。最惨的一次我帮同事排查一台 CentOS 机器发现 mysqld_safe 起不来原因是 5.7 的初始化脚本被 8.0 的 RPM 包覆盖了一部分两个版本的 systemd 服务文件同时在打架前后修了三个小时。相比之下Docker 镜像把整个运行环境打包好了。拉一个官方 mysql 镜像里面自带 MySQL 二进制、依赖库、默认配置和初始化逻辑。宿主机上只需要 Docker 运行时不需要安装任何 MySQL 相关组件。升级版本时直接换镜像 tag回滚时停掉新容器、用旧 tag 重新跑一个即可整个系统的依赖冲突问题基本消失。1.2 容器化带来的核心收益用 Docker 跑 MySQL最直接的收益是环境一致性。开发、测试、生产只要用同一个镜像版本运行行为基本一致“我本地是好的”这类争议会少很多。另一个收益是运维动作标准化启动、停止、删除、重建都是 docker 命令不依赖系统服务管理器的差异在 Ubuntu 上和在 CentOS 上操作完全一致。容器的资源隔离也让多实例部署变得很优雅。同一台机器上想同时跑 MySQL 5.7 和 8.0 做对比传统方式要处理端口、目录、配置文件冲突Docker 只需要映射两个不同的宿主机端口数据目录分开挂载几分钟就能搞定。开发环境需要临时开一个 MySQL 实例测数据迁移脚本用完直接删掉完全不影响主环境。但这里有个关键前提Docker 容器默认是“无状态”的。容器一旦被删除容器内写入的数据全部消失。所以如果要用 Docker 跑 MySQL数据持久化不是可选项而是必选项。这也是这篇文章后面重点展开的内容。理解了这个前提你才能真正安全地使用容器化数据库。1.3 不是所有场景都适合容器化尽管 Docker 跑 MySQL 在很多场景下很香但我不建议无脑容器化所有数据库。如果你的业务是写密集型、对 IOPS 和延迟极其敏感的大型生产库容器化会引入额外的存储驱动开销和网络代理开销调优路径也比物理机复杂。如果公司已经有成熟的物理机数据库运维体系监控、备份、高可用都绑定在系统服务上强行迁移到容器反而增加了运维成本。我的建议是本地开发、测试环境、内部中小型业务系统用 Docker 跑 MySQL 完全够用配合持久化挂载和定时备份可靠性完全能接受。生产环境要评估实际情况至少要做好性能压测、备份恢复演练、监控接入这三件事再上线。不要因为“容器化很流行”就盲目迁移数据库的稳比炫技重要得多。2. 数据持久化理解容器为什么一删就没2.1 容器文件系统的读写机制要理解持久化先得搞明白容器的文件系统模型。Docker 镜像是一层一层只读的比如 mysql:8.0.36 镜像底层有操作系统基础层、MySQL 程序层、配置层等。容器运行时Docker 在最上层叠加一个“可写层”所有在容器内产生的文件改动都写在这个可写层里。这个可写层和镜像层是松耦合的。容器正常运行时一切正常但一旦执行 docker rm 删除容器可写层连同里面所有数据会一起销毁。你可以把这种机制理解成在咖啡馆的草稿纸上写东西纸是咖啡馆提供的你离开后这张纸会被收走内容也随之消失想长久保存就得把内容誊写到自己的笔记本上。容器的“笔记本”就是数据卷和挂载目录。docker commit 可以把当前容器的可写层固化成新镜像有人用它来“保存”容器状态但这对数据库来说是个坏习惯。MySQL 数据文件状态复杂commit 出来的镜像层既不能保证数据一致性又会在镜像仓库里堆积大量垃圾层更不适合做备份。真正可靠的方案只有一个把数据写到容器外部的持久化存储上。2.2 数据卷和绑定挂载怎么选Docker 提供两种持久化方式数据卷volume和绑定挂载bind mount。数据卷由 Docker 管理数据存放在 /var/lib/docker/volumes/ 目录LinuxWindows 和 Mac 则由 Docker Desktop 管理。绑定挂载直接把宿主机某个目录或文件挂进容器路径由你自己指定数据也存在指定的宿主机位置。对比项数据卷volume绑定挂载bind mount数据位置Docker 管理目录宿主机自定义路径备份/迁移需要用 docker run --volumes-from 或拷贝卷目录直接操作宿主机文件夹宿主机直接查看数据库文件需要进 Docker 目录跨平台路径不一致直接 ls 就能看到权限控制Docker 统一处理跨平台较友好需要手动处理 用户/权限适合场景compose 编排、多平台复用想直接放配置文件、导出备份到宿主机我自己的使用习惯是配置目录和日志目录用绑定挂载因为 my.cnf 文件放在宿主机上方便直接编辑和版本管理日志文件也想用 logrotate 统一处理数据目录在单机部署时也用绑定挂载路径透明直观出问题定位方便。在 docker-compose 里用命名卷也很常见但如果你在 Windows/Mac 和 Linux 之间切换环境named volume 的跨平台性更好。两者没有绝对优劣选一个顺手的方式关键在于明白数据存在哪里、怎么备份。2.3 MySQL容器里哪些目录值得持久化跑 MySQL 容器时最核心的持久化目录是 /var/lib/mysql这是 MySQL 的数据目录包括系统数据库、用户业务库表文件、InnoDB 的 redo log、undo log以及 binlog 日志文件都在这里。如果只挂载一个目录那一定是它。第二个建议持久化的是自定义配置目录。MySQL 官方镜像默认在 /etc/mysql/conf.d/ 下加载额外的 .cnf 配置你可以把宿主机上的 my.cnf 文件挂载到这个目录下或者直接挂载单个文件比如-v /data/mysql/my.cnf:/etc/mysql/conf.d/my.cnf:ro。这样容器重启、重建后配置依然生效不用每次手动 docker exec 进去改配置。第三个是日志目录。MySQL 的慢查询日志、错误日志默认写到 stderr 或数据目录如果你显式配置了 slow_query_log_file 等日志路径建议把这些日志挂载到宿主机指定目录避免容器重建后日志丢失也方便统一采集。需要注意的是权限设置要匹配 MySQL 容器内 mysql 用户的 UID通常是 999否则启动时可能报权限错误。3. 实操从拉镜像到验证数据持久化3.1 环境准备先确认Docker真的能跑动手之前先确认 Docker 环境。Linux 服务器上执行docker --version和docker compose version如果命令不存在需要先安装 Docker Engine安装后执行systemctl enable --now docker设置开机自启。Windows 和 Mac 一般用 Docker Desktop。Windows 上经常遇到一个经典问题启动 Docker Desktop 时提示 “Virtualization support not detected docker desktop failed to start because …”。这个报错的核心是虚拟化没开或者没对 Windows 功能做完整配置。解决思路是进入 BIOS 开启 Intel VT-x 或 AMD-V在 Windows 功能里启用“虚拟机平台”和“适用于 Linux 的 Windows 子系统”WSL2必要时执行bcdedit /set hypervisorlaunchtype auto重置 Hypervisor 启动类型然后重启。环境装好后可以用docker run hello-world做一次冒烟测试。如果镜像拉取失败大概率是网络问题需要先配置 Docker 镜像加速或代理。确认 Docker 能正常拉镜像、跑容器再进入下一步否则后面所有操作都会卡在第一步。3.2 镜像版本选择别稀里糊涂拉latestDocker Hub 上 MySQL 官方镜像主要有 5.7 和 8.0 两个大版本。8.0 是当前主流支持窗口函数、CTE公共表表达式、默认认证插件是 caching_sha2_password5.7 是老项目兼容性更好的选择但官方社区版维护已经逐渐收尾不建议新项目再用。对比项mysql:5.7mysql:8.0默认认证插件mysql_native_passwordcaching_sha2_password新特性支持基本停滞窗口函数、CTE、Hash Join老客户端兼容性好旧版客户端可能连不上推荐场景存量老项目新项目、测试开发还有一点容易被忽略不要用 latest 标签。latest 会跟随官方发布漂移你可能今天拉的是 8.0.36过几个月再拉就变成 8.4 或者更高版本小版本升级带来行为变化。建议锁定具体版本号比如mysql:8.0.36这样镜像内容可预期回滚也方便。确认版本后最好先docker pull mysql:8.0.36把镜像下载到本地避免 run 的时候因为网络问题卡住。3.3 创建挂载目录和配置文件我习惯把 MySQL 相关文件统一放在 /data/mysql 下子目录分为 data、conf、logs 三个。先创建目录然后处理数据目录的权限。mkdir -p /data/mysql/{data,conf,logs} chown -R 999:999 /data/mysql/data chmod 750 /data/mysql/data这里解释一下权限MySQL 官方镜像内的运行用户是 mysqlUID 固定为 999。如果宿主机的数据目录是 root 所有容器内的 mysql 进程就没有写权限初始化时会直接报错退出。chown 到 999 是官方镜像采用的约定方式这样宿主机目录和容器内用户权限就能对齐。如果系统里有其他进程恰好用了 999 这个 UID要提前检查冲突避免误伤其他应用。然后在 conf 目录下准备一个 my.cnf内容按需配置。我常用的基础配置如下[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci default-time-zone08:00 max_connections200 slow_query_log1 slow_query_log_file/var/log/mysql/slow.log long_query_time1 [client] default-character-setutf8mb4这里有几个细节。default-time-zone 用08:00而不是Asia/Shanghai原因是 MySQL 的命名时区支持依赖操作系统时区表容器里如果没有完整加载 tzdata可能不生效而08:00这种偏移量写法在任何环境都能直接识别。慢查询日志路径我单独配置到了 /var/log/mysql 下这个目录会挂载到宿主机 /data/mysql/logs方便运维排查。3.4 运行容器一条命令拆开讲目录和配置准备好之后执行下面的命令启动 MySQL 容器docker run -d \ --name mysql8 \ -p 3306:3306 \ -p 33060:33060 \ -e MYSQL_ROOT_PASSWORDYourStrongPassw0rd \ -e MYSQL_ROOT_HOST% \ -e TZAsia/Shanghai \ -v /data/mysql/data:/var/lib/mysql \ -v /data/mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro \ -v /data/mysql/logs:/var/log/mysql \ --restart always \ mysql:8.0.36参数逐条说明。-d表示后台运行--name mysql8给容器命名后续管理都用这个名字。-p 3306:3306把宿主机 3306 映射到容器 3306-p 33060:33060映射 MySQL X Protocol 端口如果不用 X Protocol 可以不加。-e MYSQL_ROOT_PASSWORD设置 root 初始密码首次初始化时生效-e MYSQL_ROOT_HOST%允许 root 从任意主机远程连接仅在内网环境建议这么用生产环境建议改成具体网段或者用业务账号。三个-v是持久化的核心数据目录挂载、配置单文件挂载、日志目录挂载。配置单文件挂载比挂载整个目录更安全因为整个目录挂载会覆盖镜像原有的 conf.d 内容可能出现配置丢失或加载异常。--restart always让 Docker 在容器异常退出或宿主机重启时自动拉起容器相当于给 MySQL 加了自动恢复能力。首次执行后官方镜像的 entrypoint 脚本会发现数据目录为空自动执行 MySQL 初始化流程包括创建系统表、设置 root 密码等。可以用docker ps查看容器状态再用docker logs mysql8看初始化日志。3.5 持久化验证重启、删除、重建三步走容器起来后不要急着相信“数据已经持久化”一定要做一轮完整验证。我通常按三步走。第一步是基础写入。进容器连接 MySQL创建一个测试库和测试表写入一条数据docker exec -it mysql8 mysql -uroot -pCREATE DATABASE test_persist; USE test_persist; CREATE TABLE t_demo (id INT PRIMARY KEY, name VARCHAR(50)); INSERT INTO t_demo VALUES (1, hello docker mysql);第二步验证重启。执行docker restart mysql8容器重启完成后再次查询数据确认数据还在。这个步骤验证的是容器异常退出、命令重启后数据不丢。第三步是关键模拟最极端的情况——删除容器再重建。执行docker rm -f mysql8强制删除容器然后再执行和上一步完全相同的 docker run 命令重新启动容器。注意数据目录还是同一个 /data/mysql/data所以容器初始化脚本检测到数据目录非空就不会重新初始化而是直接启动已有数据。进入容器查询刚才的 test_persist 表数据应该原封不动。这套验证做完才是真正放心地把业务数据交给 Docker 容器。我踩过没做验证就直接上线的坑后来清理无用容器时误删了数据目录虽然最终从备份里恢复了但那一次的经历让我养成了“恢复演练优先”的习惯。3.6 备份与恢复写在最前面的保命技能持久化解决的是“容器删了数据还在”的问题但解决不了“磁盘坏了、目录被误删”的灾难。数据库的备份与恢复必须提前准备。用 Docker 跑 MySQL 后备份命令有一点小变化但逻辑和传统方式一致。全量备份用 mysqldump执行宿主机上的重定向把备份文件直接写到宿主机目录docker exec mysql8 sh -c exec mysqldump -uroot -p$MYSQL_ROOT_PASSWORD --all-databases --single-transaction --routines --triggers /data/mysql/backup_$(date %F).sql--single-transaction对 InnoDB 表做一致性快照备份避免备份过程中数据不一致--routines和--triggers把存储过程和触发器一起备份容易漏。恢复时用 mysql 客户端读入备份文件docker exec -i mysql8 sh -c exec mysql -uroot -p$MYSQL_ROOT_PASSWORD /data/mysql/backup_2024-01-01.sql注意这里用了-i而不是-it目的是保持标准输入重定向不要分配伪终端。定期备份可以写到 crontab 里保留最近 N 份。有条件的话备份文件每天同步到另一台机器或对象存储防止宿主机本身出问题。4. 常见问题与排查技巧实录4.1 容器起不来先看日志遇到容器启动失败或初始化失败第一件事永远是看日志不要瞎猜。执行docker logs --tail 50 mysql8日志里会直接给出 MySQL 的错误输出。我见过的几类高频问题数据目录权限不对日志会报[ERROR] Failed to open file .../ibdata1或mysqld: Cant create/write to file/isnt a directory挂载目录被宿主机其他进程占用或格式不对会报[ERROR] InnoDB Operating system error number 13初始化阶段数据目录非空但不完整会提示类似 “Temporary file for –create-options’ could not be created” 或者版本不匹配的错误。日志是定位问题的第一入口养成先看日志、再动手改配置的习惯。4.2 宿主机端口被占用容器启动时报Bind for 0.0.0.0:3306 failed: port is already allocated说明宿主机 3306 端口已被占用。先用ss -lntp | grep 3306或netstat -tlnp | grep 3306找出占用进程。如果是宿主机之前装过 MySQL需要停掉旧服务如果只是端口被其他应用占用最简单的处理是换宿主机端口映射比如-p 3307:3306容器内 MySQL 端口保持默认 3306 不变外部通过 3307 连接。换端口的时候注意防火墙安全组。云服务器需要放行新端口本地防火墙如果开启也要同步调整。很多人在本机改了端口后连接不上排查半天才发现是 firewall 没放行。4.3 客户端连不上认证插件、SSL错误和Socket问题客户端连接报Authentication plugin caching_sha2_password cannot be loaded这是 MySQL 8.0 默认认证插件和旧客户端不兼容导致的。老版本的 Navicat、旧版 JDBC 驱动、某些老语言库不认识 caching_sha2_password。解决方法是给用户改成旧的 mysql_native_passwordALTER USER root% IDENTIFIED WITH mysql_native_password BY 你的密码; FLUSH PRIVILEGES;新版客户端可以直接支持 caching_sha2_password不需要改。连接池场景下要注意多个连接同时建立时如果驱动不支持新插件会出现随机连接失败的诡异现象不只是单个连接报错这一点在排查“应用偶发断连”时值得优先考虑。另一个高频问题本地命令行连接报ERROR 2002 (HY000): Cant connect to local MySQL server through socket /var/run/mysqld/mysqld.sock。这个报错发生在宿主机上直接执行mysql -uroot -p时因为宿主机没有 MySQL 客户端的 socket 文件MySQL 默认走 unix socket 连接方式。在宿主机上连接容器内的 MySQL应该用 TCP 方式并指定端口mysql -h 127.0.0.1 -P 3306 -uroot -p至于 mysql ssl连接错误ERROR 2026 (HY000): SSL connection error可能原因包括客户端和服务端的 SSL 协议不匹配、证书时间问题、中间设备拦截。短时间内定位问题可以临时禁用 SSL 验证mysql --ssl-modeDISABLED -h 127.0.0.1 -P 3306 -uroot -p如果禁用后能正常连接说明问题出在 SSL 通道本身需要检查证书配置。生产环境不建议长期禁用 SSL应该配置正确的 CA 证书并让客户端校验。4.4 时区和字符集问题新容器连接后发现数据少 8 小时大概率是时区没有配置到位。排查命令SHOW VARIABLES LIKE %time_zone%; SELECT NOW();如果显示 SYSTEM 或 UTC说明 MySQL 会话时区不是东八区。解决方案有几个层面容器启动时加-e TZAsia/Shanghai设置系统时区配置文件里写default-time-zone08:00设置 MySQL 默认时区应用连接串里追加serverTimezoneAsia/Shanghai或connectionTimeZoneAsia/Shanghai。我建议前两个都做三层覆盖最稳。字符集乱码也是高频问题。检查SHOW VARIABLES LIKE character%;如果 character_set_server 不是 utf8mb4需要修改配置重启。已经创建的库和表不会自动跟随默认字符集改变要手动转换ALTER DATABASE yourdb CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; ALTER TABLE yourtable CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;CONVERT TO会重写表数据大表操作前务必先备份、业务低峰期执行。字段的默认值问题也经常伴随字符集出现比如设置默认值 0 时ALTER TABLE ... ALTER COLUMN col SET DEFAULT 0但已有数据不受影响只影响后续新插入的行这是很多人的误区。4.5 Docker网络不通怎么办容器化之后网络问题比传统部署多一些。先说跨容器访问一个 Web 容器要连 MySQL 容器如果直接用localhost连必然失败因为两个容器各自有独立网络命名空间。正确做法是创建一个自定义 bridge 网络把两个容器都加进去然后用容器名作为主机名访问。docker network create mynet docker run -d --network mynet --name mysql8 ... mysql:8.0.36 docker run -d --network mynet --name webapp ... your-webappWeb 应用连接串里数据库地址写mysql8而不是 IP。同一个宿主机上的外部应用要连 MySQL用127.0.0.1和映射端口即可。其他机器连不上先查宿主机的防火墙和安全组再查 MySQL 的 root host 是否允许该 IP 来源。容器内访问外网失败则查看宿主机 DNS 配置镜像是精简版可能没有内置 dig、ping可以用docker exec mysql8 cat /etc/resolv.conf确认 DNS 设置必要时加--dns参数覆盖。4.6 Windows Docker Desktop的坑Windows 上跑 Docker Desktop 有几个独特问题。启动报错Failed to connect to the docker api at npipe:////./pipe/dockerDesktopLinuxEngine一般出现在 Docker 引擎还没完全启动时处理方式是重启 Docker Desktop检查系统托盘图标是否变成稳定状态或者切回 Linux Container 模式后再切回来。WSL2 后端下如果 MySQL 数据挂在 /mnt/c 这类 Windows 文件系统上I/O 性能会明显下降因为 WSL2 的跨文件系统读写性能本来就不太行。这时把数据目录放在 WSL2 的虚拟磁盘里或者直接用 named volume性能会好很多。另外 WSL2 默认占用内存较高可以在用户目录下建 .wslconfig 限制[wsl2] memory4GB swap2GB改完执行wsl --shutdown重启 WSL 生效。4.7 常见报错速查表报错现象常见原因处理方式docker: Error response from daemon: driver failed programming external connectivity端口映射冲突查看端口占用换宿主机端口[ERROR] InnoDB: Operating system error number 13数据目录权限不足chown -R 999:999 数据目录Authentication plugin caching_sha2_password cannot be loaded客户端版本过旧升级客户端或改用户认证插件ERROR 2002 Cant connect via socket宿主机上没用 TCP 连接用 -h 127.0.0.1 -P 3306ERROR 1130 Host not allowed to connectroot 主机限制用 MYSQL_ROOT_HOST% 或授权用户ERROR 2026 SSL connection errorSSL 协议或证书问题临时 ssl-modeDISABLED 定位5. 进阶从“能用”到“好用”的几个习惯5.1 用docker-compose替你把参数管起来docker run 命令参数多记不住也容易打错。我更推荐用 docker-compose 管理 MySQL 容器所有配置收敛在一个 yaml 文件里配合 Git 做版本管理换机器迁移环境直接复制文件再docker compose up -d就能复现。services: mysql8: image: mysql:8.0.36 container_name: mysql8 restart: always ports: - 3306:3306 environment: - MYSQL_ROOT_PASSWORDYourStrongPassw0rd - MYSQL_ROOT_HOST% - TZAsia/Shanghai volumes: - ./data:/var/lib/mysql - ./my.cnf:/etc/mysql/conf.d/my.cnf:ro - ./logs:/var/log/mysql command: - --character-set-serverutf8mb4 - --collation-serverutf8mb4_unicode_ci healthcheck: test: [CMD, mysqladmin, ping, -h, localhost] interval: 30s timeout: 5s retries: 5healthcheck是容器健康检查MySQL 实例就绪后 mysqladmin ping 才会返回成功。Web 应用依赖数据库启动时可以通过depends_on配合 healthcheck 条件等待数据库就绪避免应用启动时数据库还没初始化完成。用docker compose logs -f mysql8查看日志docker compose down停止容器注意down不会删除挂载目录里的数据这是数据持久化的保障。5.2 给容器戴上“紧箍咒”资源限制MySQL 是吃内存大户尤其是 InnoDB 的缓冲池会按配置申请内存。如果宿主机同时跑多个容器一个没有限制的 MySQL 容器可能把机器内存吃光拖垮其他应用。建议启动时加资源限制docker run -d ... --memory2g --cpus2 mysql:8.0.36在 compose 里对应deploy: resources: limits: memory: 2g cpus: 2.0注意 InnoDB buffer pool 大小要配合容器内存限制设置。如果容器限制内存 2G而 MySQL 默认 buffer pool 是 128M配置 low 一点问题不大但如果你调高了 buffer pool同时又把容器内存限制得很小MySQL 可能因为内存不足拒绝写入或直接崩溃。资源限制是对宿主机的保护配置参数是对 MySQL 的保护两者要联动设置。5.3 日志和存储空间别等你主动去清容器化之后MySQL 的错误日志和慢查询日志如果没有挂载会写到容器内容器删除就没了如果输出到 stdout则被 Docker 收集到 json-file 日志里长时间运行会让宿主机磁盘出现大量日志文件。排查日志最常用的是docker logs --tail 100 mysql8 docker logs --since 30m mysql8更合理的做法是给容器日志设置滚动上限docker run -d \ --log-opt max-size50m \ --log-opt max-file3 \ ... mysql:8.0.36compose 里写logging: driver: json-file options: max-size: 50m max-file: 3这样每个日志文件最多 50MB保留 3 个磁盘不会被日志撑爆。另一个空间黑洞是 binlog。MySQL 默认在数据目录下生成 binlog如果开启了 binlog 且没有设置过期时间日积月累会占用大量磁盘。建议在 my.cnf 中设置binlog_expire_logs_seconds 604800保留 7 天。注意 MySQL 8.0 里 expire_logs_days 已经废弃用 binlog_expire_logs_seconds 替代。5.4 初始化和安全方面的两个加分项MySQL 官方镜像支持在首次初始化时执行 /docker-entrypoint-initdb.d 目录下的脚本包括 .sql 和 .sh 文件。这个特性很适合在数据库第一次初始化时自动创建业务库和业务用户。比如准备一个 init.sqlCREATE DATABASE IF NOT EXISTS appdb DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE USER appuser% IDENTIFIED BY AppUser123; GRANT ALL PRIVILEGES ON appdb.* TO appuser%; FLUSH PRIVILEGES;启动容器时挂载-v /data/mysql/init:/docker-entrypoint-initdb.d:ro注意这个目录只会在数据目录为空、数据库首次初始化时执行如果数据目录已经初始化过脚本不会再执行。往已有数据的环境里追加初始化脚本是无效的。安全方面环境变量传递密码虽然方便但容易在 shell history 里留下痕迹。更稳妥是用 env_file 指定环境变量文件并在 Git 里忽略该文件。另外我习惯为业务单独创建账号而不是让应用直接使用 root。root 只用于运维操作业务账号按最小权限原则授权。容器端口也尽量不要对公网开放通过 Docker 内部网络让应用容器直连 MySQL不映射外部端口或只绑定内网 IP这是最省事也最安全的方式。最后再分享一个我自己的经验用 Docker 跑 MySQL 确实舒服但真正决定数据安全的从来不是“用不用 Docker”而是你有没有想清楚数据放在哪里、有没有做备份和恢复演练。我试过在没有任何持久化挂载的情况下直接删容器也见过同事因为挂载目录权限不对把数据库初始化两遍这些坑都不难绕开但前提是先把持久化验证当成部署流程的一部分。如果你第一次在 Docker 里跑 MySQL建议在业务数据迁入之前按下文 3.5 节的做法完整走一遍重启、删容器、重建的验证。另外一个小技巧把docker ps -a和docker logs --tail 50记成常用命令容器出了问题先看这两条输出的习惯能帮你省下大量排查时间。