
从第一次接触 Docker 就被它折腾得够呛到现在把开发环境里的 MySQL 完全容器化这中间踩过的坑、走过的弯路还挺多的。这篇东西不是官方文档的搬运而是我实际把 MySQL 跑在 Docker 里的完整记录——从装 Docker 开始到镜像选型、容器启动、自定配置文件、进去执行 SQL再到排查各种启动失败的问题一条线走到底。如果你正准备在自己电脑上装 MySQL 但不想被原生安装的依赖、目录、权限搞到头疼或者想在服务器上用容器统一管理数据库这篇内容可以直接照着操作。1. 为什么我建议 MySQL 这种重服务也交给 Docker 跑很多人一听到容器化就觉得是生产环境、微服务才需要的东西本地装个数据库直接下载安装包不就完事了。我以前也是这么想的直到帮同事修过几次 MySQL 安装问题后彻底改掉了这个习惯。1.1 原生安装的体验到底有多折腾Windows 上装 MySQL看起来是下一步下一步但后面等着你的往往是一串莫名其妙的问题安装目录带空格导致服务起不来、my.ini 找不到、服务管理器里 MySQL 服务消失了、卸载不干净重装的时候残留注册表报错……我在 Windows 上遇过最离谱的一次是某次安全软件更新后 MySQL 服务直接启动失败日志里写的错误信息跟实际情况完全对不上最后只能重装系统级的卸载清理。Linux 服务器上稍微好一点但也要处理 apt 源、依赖包、配置文件权限、开机自启脚本这些事。如果你需要在同一台机器上区分测试环境和开发环境或者版本之间来回切换原生安装基本是噩梦——装一个 5.7 再用 8.0 就得先卸载数据目录、配置文件全搅在一起。1.2 容器方案解决了哪些实际问题把 MySQL 放进 Docker 之后我最直观的感受是三个字可复用。一条docker run命令或者一个docker-compose.yml文件就能在任何装好 Docker 的机器上把一模一样的数据库环境拉起来。Windows 上遇到的问题在 Mac 上不会重现测试环境的版本和开发环境完全一致不再出现我本地好好的到服务器上就报错这种经典问题。数据怎么办这也是很多人担心的。MySQL 容器本身是个一次性的东西删掉重建都行但数据必须放在宿主机上——也就是挂载数据目录这个后面会重点讲。设置的密码通过环境变量控制自定义配置通过文件挂载注入端口映射表做好规划这套组合下来MySQL 在 Docker 里比原生安装更清晰想换版本换镜像标签重跑一次想备份直接备份宿主机的数据目录想删环境停容器删目录干干净净没有残留。我自己现在开发机上跑着两个 MySQL 容器一个 5.7.44一个 8.0互不干扰随时可以同时启动。这在原生安装下要折腾一整天容器方案十分钟搞定。2. 环境准备与镜像选型跑 MySQL 之前先想清楚这两件事动手拉镜像之前先把底下的运行时准备好。很多新手第一步就卡在docker 命令输入后提示找不到或者安装完 Docker Desktop 后一直启动不了。这些不是 MySQL 的问题是 Docker 本身没装对。2.1 Docker Desktop 安装时最容易忽略的选项如果你用的是 Windows我强烈建议直接装 Docker Desktop不要手动折腾 WSL 里面跑 dockerd。Docker Desktop 安装包下载没什么难度但有两个细节容易被跳过第一个是安装过程中会让你选择是否启用 WSL 2 集成。WSL 2 是 Windows 上跑 Linux 容器的推荐后端性能比之前的 Hyper-V 方案好内存占用也更合理。这个选项一定要勾上。装完 Docker Desktop 之后打开 Settings - Resources - WSL Integration确保你常用的发行版比如 Ubuntu的集成开关是打开的同时勾选Enable integration with my default WSL distro。第二个是安装完成后 Docker Desktop 不会自动给你配置国内镜像加速。这个不配也能用但拉 mysql 这种几百 MB 的镜像时速度会很感人。我在 Docker Desktop 的 Settings - Docker Engine 里直接编辑 JSON 配置加入了自己的镜像加速地址保存后重启引擎生效。Mac 用户省心很多直接装 Docker Desktop for Mac没有 WSL 这层概念。Linux 用户则建议直接用包管理器安装docker.io或者docker-ce然后把自己的用户加入docker组不然每次都要sudo。装完之后验证一下环境docker --version docker run hello-worlddocker run hello-world能正常输出提示信息说明引擎、网络、镜像拉取都没问题。如果这一步卡住先检查 Docker Desktop 图标是否常驻运行再检查网络和镜像加速配置不要急着继续往下走。2.2 mysql 镜像版本怎么挑8.0 还是 5.7MySQL 官方在 Docker Hub 上维护的镜像标签规则很清晰常用的有标签说明适用场景mysql:8.08.0 系列最新版当前 8.0.x新项目首选官方主推版本mysql:8.48.4 LTS 版本需要长期维护的生产环境可以考虑mysql:5.75.7 系列最新版现在停在 5.7.44老项目兼容Oracle 已停止更新但仍有大量存量使用mysql:latest指向当前最新大版本不建议用在正式环境因为版本漂移不可控有基础但还在纠结选哪个的读者记住这个原则就够用了新环境无脑上 8.0老代码依赖 5.7 才必须用 5.7。8.0 默认存储引擎是 InnoDB默认字符集在 8.0 里也变成了 utf8mb4更早的 5.7 默认是 latin1中文乱码的根源之一就是没改字符集。还有个实际影响很大的差异MySQL 8.0 默认用的认证插件是caching_sha2_password而 5.7 用的是mysql_native_password。如果你打算用老版本的客户端工具比如某些旧版 Navicat、PHP 5.x 的 mysqli连接 8.0会直接报Authentication plugin caching_sha2_password cannot be loaded这种错误。这个后面讲外部连接的时候再给解决方案。拉镜像的指令很简单docker pull mysql:8.0 docker pull mysql:5.7我建议把两个版本都拉下来备用反正不占太多磁盘空间。测试下来mysql:8.0镜像大小在 200 MB 左右mysql:5.7稍小一点。拉镜像时如果速度慢就是 2.1 里说的镜像加速没配好。3. 启动容器一条 docker run 命令背后的参数拆解这是全篇的重头戏。我会把完整命令摆出来然后逐个参数拆开讲确保你不仅知道要写什么更知道为什么这么写。3.1 一条跑通的基础命令以 MySQL 8.0 为例我开发机上最早用的启动命令是这样的docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123456 \ -e TZAsia/Shanghai \ -v /d/docker/mysql8/data:/var/lib/mysql \ -v /d/docker/mysql8/conf:/etc/mysql/conf.d \ --restartalways \ mysql:8.0我已经尽量去掉不必要的参数让它保持精简但每个留下的参数都值得说明。前后顺序无所谓Docker 会自行解析关键是语义要清楚。3.2 各参数的实际作用与原理-ddetached 模式容器在后台运行。如果不加这个参数前台模式会持续输出 MySQL 的日志流并把当前终端占住CtrlC 就会把容器也停掉。作为数据库服务通常都在后台跑。--name mysql8给容器起一个固定的名字。有了名字后面执行docker exec -it mysql8 ...、docker stop mysql8就都不需要查容器 ID 了。容器删除后这个名字才能复用如果你删了一个叫 mysql8 的容器再创建同名的会提示名字冲突。-p 3306:3306端口映射格式是宿主机端口:容器端口。MySQL 默认监听容器的 3306 端口映射到宿主机之后你的程序只需要连localhost:3306就能访问到容器里的 MySQL。映射到 3307 的例子也很常见——当你已经有另一个 MySQL 占用宿主机 3306 时可以这样docker run -d \ --name mysql8_test \ -p 3307:3306 \ -e MYSQL_ROOT_PASSWORDroot123456 \ mysql:8.0-e MYSQL_ROOT_PASSWORDroot123456设置 root 用户的初始密码。MySQL 官方镜像要求必须设置这个环境变量或者设置MYSQL_ALLOW_EMPTY_PASSWORDyes允许空密码生产环境千万别干这种事。这里有个极其重要的坑MYSQL_ROOT_PASSWORD 只在数据目录首次初始化的时候生效。也就是说如果/var/lib/mysql挂载目录里已经有一份初始化过的数据了你再改这个环境变量重启容器密码不会变。-e TZAsia/Shanghai设置容器时区。不设的话容器默认 UTC 时间MySQL 的NOW()函数返回的和北京时间相差 8 小时。这个坑很隐蔽往往在保存时间字段的时候才暴露。-v /d/docker/mysql8/data:/var/lib/mysql绑定挂载数据目录。/d/docker/...是 Git Bash 风格的 Windows 路径也就是D:\docker\...。冒号左边是宿主机目录右边是容器内 MySQL 的数据目录。MySQL 镜像里默认数据目录就是/var/lib/mysql初始化时会把全部数据库文件写到这个目录。挂载之后容器删除、重建数据仍然留在宿主机目录里。-v /d/docker/mysql8/conf:/etc/mysql/conf.d挂载自定义配置目录。镜像里的 MySQL 会扫描/etc/mysql/下的所有.cnf文件其中/etc/mysql/conf.d/是专门给用户放自定义配置的目录。你找一个宿主机目录放my.cnf容器启动时就会自动读取。--restartalways自动重启策略。Docker 服务重启、宿主机重启后这个容器会跟着自动启动容器异常退出时 Docker 也会尝试拉起它。对数据库这种长期运行的服务来说这个参数能让你的环境少很多早上起来发现没启动的尴尬。3.3 数据目录挂载的权限问题Linux 环境下直接挂载数据目录可能会遇到一个 8.0 镜像特有的问题镜像内的 MySQL 进程默认以mysql用户运行uid/gid 在 999 或者 27 附近不同版本不同。如果你宿主机上的挂载目录归属是 root 或者其他用户容器启动时会报mysqld: Cant read dir of /var/lib/mysql/这类权限错误。解决方式很简单直接给挂载目录授权chown -R 999:999 /your/path/mysql8/data如果你的 Linux 发行版用过别的 uid可以在镜像里确认docker run --rm mysql:8.0 id mysql出来结果按实际 uid/gid 授权即可。Windows 和 Mac 上因为 Docker Desktop 做了文件系统桥接一般不涉及这个问题但也不要随便把目录放到系统保护路径下。4. 自定义配置my.cnf 挂载与常见配置项容器跑起来只是第一步真正让 MySQL 按你的习惯工作靠的是配置文件。原生安装你去翻my.ini或者/etc/my.cnf容器方案里你只需要准备一个目录和一份文本文件启动时挂进去。4.1 配置文件放置方式与读取顺序MySQL 官方镜像里读取配置的位置是从/etc/mysql/目录开始的。默认的my.cnf会 include/etc/mysql/conf.d/下的所有.cnf文件这是官方的扩展点也是我推荐你挂载的地方。具体做法在宿主机创建配置目录和文件mkdir -p /d/docker/mysql8/conf然后新建一个my.cnf文件放在/d/docker/mysql8/conf/下面写入你要的配置项。启动命令里的-v /d/docker/mysql8/conf:/etc/mysql/conf.d会把整个目录映射进去文件里写的所有配置会在启动时被读取。如果你不想走目录挂载也可以直接挂单个文件-v /d/docker/mysql8/my.cnf:/etc/mysql/conf.d/my.cnf但我不推荐这种写法因为容器内那个文件路径如果不存在Docker 会帮你创建一个空目录Windows 下尤其容易出幺蛾子导致 MySQL 找不到配置。目录整体挂载最稳。4.2 常用配置项怎么选最不容易出问题按我的习惯一份开发环境的my.cnf通常会包含这几组内容[mysqld] character-set-serverutf8mb4 collation-serverutf8mb4_unicode_ci [mysql] default-character-setutf8mb4 [client] default-character-setutf8mb4字符集这块必须强调MySQL 8.0 虽然默认数据库字符集是 utf8mb4但服务器层的character_set_server在 8.0.1 之前默认还是 latin1如果你不想每次建库都手动指定字符集最好在配置里写清楚。utf8mb4 和 utf8mb4_unicode_ci 的组合是目前最通用的支持表情符号和大部分 Unicode 字符。再把常用配置项按是否需要分组列出来配置项作用我是否在开发环境开max_connections200最大连接数默认 151开发环境够用一般不开innodb_buffer_pool_size1GInnoDB 缓冲池大小默认 128M根据机器内存开我同事的 8G 内存本开了 1G 没问题sql_modeSTRICT_TRANS_TABLES,NO_ZERO_DATE控制 SQL 严格模式开避免脏数据写入skip-name-resolve跳过主机名反解析本地开发可以开能加快连接速度log-bin/var/lib/mysql/mysql-bin开启 binlog需要做数据同步时才开一般不开我不是教你把这些一股脑写进配置。以innodb_buffer_pool_size为例如果你机器只有 4G 内存还给它分配 2GMySQL 很可能直接不启动或者 Docker 的 OOM killer 干掉容器。原则是理解之后再改不确定就保持默认。4.3 挂载后容器起不来的排查路径加了配置挂载后容器起不来这是新手最容易摔的地方。我遇过的典型报错是这样[ERROR] [MY-000067] [Server] unknown variable max_connections200看起来像是配置语法写错了。实际上这类问题的排查路径非常固定按顺序来先看容器日志docker logs mysql8日志里会精确告诉你是哪个配置项不识别、哪个变量值非法。拿到报错后先在宿主机上用mysqld --verbose --help验证你写的那条配置是否在该版本 MySQL 中支持把mysql8替换成你要测试的镜像标签docker run --rm mysql:8.0 mysqld --verbose --help | grep max_connections如果镜像内部置的 mysqld 完全不认识这个变量那就是版本差异问题删掉这条配置或者换成 8.0 支持的写法。配置文件空格、注释格式也要注意。[mysqld]小节里的缩进空格本身没问题但如果你用 Windows 记事本编辑的my.cnf保存时带上了 UTF-8 BOM或者行尾是 CRLF某些解析严格的情况会报错。我保存配置文件统一用 VS Code 或者 Notepad编码选 UTF-8 无 BOM换行符看平台——Windows 上跑 Docker Desktop 的 Linux 容器建议统一改成 LF 換行符省心。5. 进入容器执行 SQL三种姿势和适用场景容器跑起来数据库也初始化好了接下来才是日常操作查数据、跑 SQL、导数据库。很多人以为进容器执行 SQL就是docker exec -it一个命令实际上不同场景有不同的最佳姿势这里挨个讲。5.1 交互式进入容器内部执行 SQL先在宿主机执行docker exec -it mysql8 bash这会进入容器的 shell。然后在容器内执行mysql -uroot -p输入密码后就到了 MySQL 命令行界面。这是最直白的方式适合临时排查问题、看参数、跑一些交互式语句。如果不想跳到 shell 再敲一层可以直接用mysql客户端进 MySQL绕开 bash 这层docker exec -it mysql8 mysql -uroot -p这两个命令的区别在于前者先进容器系统再登录 MySQL后者直接在容器内启动 mysql 客户端进程。我喜欢用后者的一个原因是提示符少了 Bash 那层干扰操作路径更短。5.2 不需要交互直接在宿主机执行单条 SQL跑一条测试语句、查一个变量、看一个表结构完全没必要进容器直接后排执行docker exec mysql8 mysql -uroot -p123456 -e show databases; docker exec mysql8 mysql -uroot -p123456 -e select now();注意这里的-e是 MySQL 客户端的参数不是 Docker 的。Docker 把参数原样传给容器里的mysql命令。密码直接写在命令行上会出现在 shell history 里所以我自己更习惯只写-p让它交互输入或者用 MYSQL_PWD 环境变量但那个也不安全正常开发环境图省事写-p123456问题不大生产环境千万别这么干。批量执行多条 SQL 的时候写一个test.sql文件然后docker exec -i mysql8 sh -c mysql -uroot -p123456 /path/to/test.sql这里不是-it而是-i保持标准输入打开因为我们要把宿主机的文件内容作为 stdin 喂给容器里的 mysql。符号把宿主机文件内容重定向到 docker exec 的标准输入。不过这个方案有一点麻烦SQL 文件必须存在于宿主机能被 Docker Desktop 访问到的路径上Windows 下任意盘符都可以如果文件在容器内部路径里那就直接docker exec mysql8 mysql -uroot -p123456 -e source /tmp/test.sql或者进容器后source /tmp/test.sql。我实测更常用的是把 SQL 文件复制进容器再 sourcedocker cp /d/sql/backup.sql mysql8:/tmp/ docker exec mysql8 bash -c mysql -uroot -p123456 /tmp/backup.sql这个做法很适合临时导入大 SQL 文件因为docker cp不经过网络和端口路径清楚调试起来也直观。5.3 从外部用客户端连接容器 MySQL这是开发时最常用的一种方式因为你会用 Navicat、DataGrip、DBeaver 这类图形化工具看数据或者从应用代码里jdbc:mysql://localhost:3306/dbname指向容器。关键在于端口映射和认证方式。如果是 MySQL 8.0老客户端连接失败的问题前面提过解决办法有两个。一是在启动容器时就加参数让 root 用户用mysql_native_password不推荐未来版本会移除这个插件。二是进入 MySQL 里对用户单独指定认证方式ALTER USER root% IDENTIFIED WITH mysql_native_password BY root123456; FLUSH PRIVILEGES;root%是一个容易踩坑的细节。默认情况下 MySQL 容器里的 root 用户只允许localhost连接外部客户端无论如何都连不上。而官方镜像的另一个行为是如果你通过MYSQL_ROOT_PASSWORD或MYSQL_ROOT_HOST环境变量设置了 root 密码它会自动创建root%用户。所以你在启动容器时最好显式加上-e MYSQL_ROOT_HOST%表示允许所有主机连接。默认环境下如果发现用127.0.0.1能连但localhost连不上或者反过来大概率是碰到了root%和rootlocalhost两条记录并存的情况。进容器执行SELECT user, host FROM mysql.user;看到的结果里对应用户的 host 列是否包含%一目了然。没有的话就补一条CREATE USER root% IDENTIFIED BY root123456; GRANT ALL PRIVILEGES ON *.* TO root%; FLUSH PRIVILEGES;5.4 容器内路径、日志与维护入口最后补一点维护性内容。MySQL 容器内的日志默认输出到标准输出所以想看 MySQL 自身的运行日志不要进容器翻文件直接从宿主机执行docker logs mysql8镜像内 MySQL 的日志文件路径是/var/log/mysql/但在容器里看这个目录通常没什么东西因为日志默认都打到 stdout 了。如果你确实想把 MySQL 的 error log 落到宿主机文件里启动时加参数-v /d/docker/mysql8/logs:/var/log/mysql这样容器被删掉之后日志也不会丢。我在排查容器起来了但 SQL 执行报错这类问题时几乎全靠docker logs定位省去了进容器逐行查日志的麻烦。6. 实测下来最常踩的坑与排查路径写到这里必须把实战中高频出现的坑单独列一章。这些不是我编的是帮人排错时反复遇到的典型场景每一个都是血泪教训。6.1 容器起一下就退出日志怎么看、怎么定位典型现象docker start mysql8之后几秒钟容器状态变成 Exited。这类问题九成都能靠日志定位执行docker logs mysql8常见输出有这么几种对应不同处理方式日志关键字含义处理方式[ERROR] [MY-010244] [Server] Cant create/write to file /var/lib/mysql/is_writable数据目录权限或磁盘问题检查挂载目录权限、磁盘剩余空间[ERROR] [MY-011300] [Server] Plugin caching_sha2_password is not loaded认证插件配置异常重查启动参数必要时删掉数据目录重建[ERROR] [MY-010334] [Server] Could not find any file in /var/lib/mysql that matches the name ./ibdata1数据目录挂载到了空目录但初始化没完成查看目录权限、日志前面的报错[Warning] [MY-010485] [Server] Insecure configuration for --secure-file-priv普通参数警告影响不大按需处理第一次遇到数据目录挂载到的是空目录时容易懵因为没有初始化过。这时直接删掉宿主机挂载目录下的所有文件然后重新docker run不带-v数据目录或者是空目录让镜像自己初始化干净的数据文件再把挂载加回去。6.2 端口被占用Error starting userland proxy这个报错基本长这样docker: Error response from daemon: driver failed programming external connectivity on endpoint mysql8: Error starting userland proxy: listen tcp4 0.0.0.0:3306: bind: address already in use意思就是宿主机 3306 端口已经被占用。最常见的元凶是宿主机上本来就装了一个 MySQL 服务Windows 上一般是服务里那个 MySQL或者你上一个容器已经占了 3306。排查方式netstat -ano | grep 3306Windows 用netstat -ano | findstr 3306Mac/Linux 用lsof -i :3306。确认被占后两种选择停掉另一个服务或者给新容器换端口。开发环境换端口最省事启动命令改成-p 3307:3306即可程序连接时改一下端口。还有一个细节宿主机防火墙也要放开对应端口否则你从局域网另一台机器访问容器 MySQL 会被拦掉。这个坑隐蔽因为本机localhost:3306能通换局域网 IP 就不通。6.3 中文乱码与字符集问题字符集问题历史悠久但依然常见。症状是插入中文后 SELECT 出来是???或者建表时提示字符集不支持。排查和修复路径SHOW VARIABLES LIKE character_set%;如果character_set_server不是 utf8mb4先ALTER DATABASE 你的库名 CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;把库级字符集改掉。但更重要的是从根源上解决就是我前面 4.2 节提到的在my.cnf里把character-set-server和collation-server都配成 utf8mb4然后重启容器。建表语句里最好也显式指定CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(100) ) DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci;这样哪怕服务器全局配置出问题这张表也是对的。字符集问题最烦人的是改得太晚数据已经以 latin1 存进去了乱码的根源不在连接层而在存储层再改字符集不一定能修复既有数据。所以从拉镜像那天起就把字符集定好别等出问题再补救。6.4 容器重建后数据还在吗这是用户问我最多的问题也是最容易产生误解的地方。直接回答如果你用了-v /宿主/data:/var/lib/mysql挂载容器删了数据目录还在宿主机上重建时重新挂载同一目录即可。如果你没挂载数据目录docker rm容器之后整个容器内的数据会跟着消失。但注意docker rm和docker stop不同。docker stop只是停止容器还在磁盘上数据还在docker start就能重新起来。docker rm才是删除容器。数据卷docker volume是另一种更高级的持久化方式这里不展开但你记住一条原则生产环境或不能丢的数据永远不要让数据只存在于容器可写层。我自己做过一次验证性删除启动一个没挂载数据目录的 MySQL 容器创建一张表插了几条数据然后docker rm掉再docker run一个全新的同名容器——里面啥都没有。这个实验当时给我留下的印象太深了从此之后再也没有忘挂过数据目录。7. 进阶从普通 run 走向 compose 与版本升级如果你只是想在开发机上快速获得一个 MySQL 环境前面六章的内容已经足够。但既然都走通了我还想分享两个进阶点它们会帮你省掉未来更大的麻烦。7.1 docker-compose 固化你的 MySQL 环境每次用一长串docker run命令总会遇到记忆模糊的时候更糟糕的是多个容器多个配置来回切换很混乱。我的解决方式是写一个docker-compose.yml把整个环境固定下来。我开发机上的文件长这样version: 3.8 services: mysql8: image: mysql:8.0 container_name: mysql8 restart: always environment: MYSQL_ROOT_PASSWORD: root123456 TZ: Asia/Shanghai ports: - 3306:3306 volumes: - /d/docker/mysql8/data:/var/lib/mysql - /d/docker/mysql8/conf:/etc/mysql/conf.d之后启动、停止、查看状态都变成docker-compose up -d docker-compose down docker-compose psdocker-compose down默认不会删除数据卷所以这个方案的风险比直接docker run还要低——误删容器也不心疼重新up就回来了。7.2 版本升级与数据迁移的思路容器方案下升级 MySQL 版本思路是这样先停止旧容器备份数据目录整个复制一份到别处再用新版本镜像进去启动并挂载同一个数据目录启动后执行mysql_upgrade校验数据兼容性。这一步对 5.7 升 8.0 尤其重要因为 8.0 对数据字典、系统表做了重构老版本数据直接挂载上来可能报错。我实际做 5.7 升 8.0 的时候先把 5.7 容器停掉数据目录复制了一份然后用 8.0 镜像挂载原目录启动日志里出现了几个警告但 MySQL 会自动执行升级流程。跑完后执行SELECT VERSION();确认版本变成了 8.0。这里要给一个忠告升级前一定先把数据目录复制成备份我曾经图省事直接升结果 8.0 启动时发现数据不兼容只能回滚到备份目录重新来。容器化给了我们极大的容错空间别把这条路自己堵死。用 Docker 跑 MySQL 到现在我已经完全回不去原生安装 系统服务的使用方式了。最开始是图省事后来发现它带来的可移植性、环境隔离、快速重建这些能力才是真正让人上瘾的地方。如果你照着这篇文章走通了第一个容器后面遇到其他服务Redis、MongoDB、PostgreSQL你会发现套路一模一样——镜像、端口映射、数据卷挂载、配置文件注入四板斧走天下。容器化是个复利型技能MySQL 只是第一站。