用Docker Compose部署MySQL的核心思路是:把MySQL服务运行在独立的容器里,通过挂载卷把数据文件保存在宿主机上,再用端口映射暴露数据库服务。这种做法最大的好处是环境隔离,宿主机不需要预装任何MySQL相关组件,升级或卸载只需要删除容器和镜像即可。对于需要同时维护MySQL 5.7和8.0两个版本的团队来说,Compose的优势会更明显。

开始之前需要确认服务器或本地开发机已经安装了Docker Engine以及Docker Compose插件。新版Docker Desktop自带Compose功能,Linux服务器则可以通过docker compose version命令来验证。如果返回版本号,说明环境已就绪。接下来从Compose文件的基本结构入手,逐步展开配置细节。
编写docker-compose.yml文件
Compose文件是整个部署流程的核心,所有关于MySQL容器的运行参数都集中在这里。一个最小可用的配置如下所示,文件需要放在独立目录中,例如创建一个名为mysql-docker的文件夹,方便后续管理数据卷和初始化脚本。
services:
mysql:
image: mysql:8.0
container_name: mysql8
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: app_db
MYSQL_USER: app_user
MYSQL_PASSWORD: app_pass
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- ./mysql-data:/var/lib/mysql
- ./init:/docker-entrypoint-initdb.d
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-proot123456"]
interval: 10s
timeout: 5s
retries: 5
这段配置中image: mysql:8.0指定了镜像版本,写清楚版本号比直接使用latest标签更稳妥,能避免镜像更新后带来的意外行为变化。container_name固定了容器名称,后续执行docker exec等命令时不需要先查询容器ID。restart策略设为unless-stopped,表示除非手动停止,否则容器退出后会自动重启,这在服务器重启后特别有用。
环境变量部分需要注意,MYSQL_ROOT_PASSWORD是必填项,没有它容器会直接启动失败。MySQL官方镜像的入口脚本会根据这些环境变量完成初始化,包括创建数据库、创建普通用户并授权。MYSQL_DATABASE和MYSQL_USER是可选的,但强烈建议在首次启动时一并设置,因为初始化逻辑只在数据目录为空时执行一次。如果后续修改了这些环境变量,已经初始化过的数据目录不会重新执行初始化流程。
端口映射写成"3306:3306"的形式,冒号前面是宿主机端口,后面是容器内端口。如果宿主机上已经跑了一个本地MySQL服务占用了3306端口,需要把前面的端口改成3307或其他空闲端口。修改后应用连接数据库时也要使用新端口。
数据持久化与初始化脚本
数据卷挂载是决定数据安全的关键配置。./mysql-data:/var/lib/mysql把容器内的数据目录映射到宿主机当前目录下的mysql-data文件夹。这样即使容器被删除,数据库文件仍然保留在宿主机上。重新执行docker compose up时,新的容器会读取同一份数据,数据不会丢失。
需要注意的是,宿主机上的mysql-data目录初始状态必须是空的。如果这个目录里已经有旧版本MySQL生成的数据文件,比如从5.7升级到8.0时直接复用了旧数据目录,容器可能会因为数据字典版本不兼容而无法启动。正确的升级流程是先使用mysqldump导出SQL,再在新版本容器中导入。
初始化脚本挂载目录./init:/docker-entrypoint-initdb.d允许在数据库首次初始化时自动执行SQL文件。官方镜像的入口脚本会按文件名字母顺序执行该目录下所有扩展名为.sql、.sh、.sql.gz的文件。这个机制非常适合自动创建表结构、插入基础数据或者执行权限配置。
CREATE TABLE IF NOT EXISTS users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
email VARCHAR(100) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO users (username, email) VALUES
('admin', 'admin@ipipp.com'),
('guest', 'guest@ipipp.com');
把上面的SQL保存为init目录下的01-init.sql文件,首次启动容器时就会自动创建users表并插入两条测试数据。如果init目录中有多个SQL文件,建议用数字前缀控制执行顺序,比如01-schema.sql、02-seed.sql。执行完成后,可以通过查看容器日志确认脚本是否成功执行。
启动、验证与常见问题排查
在docker-compose.yml所在目录下执行docker compose up -d命令即可后台启动MySQL容器。首次执行时需要从Docker Hub拉取镜像,耗时取决于网络状况。拉取完成后,容器会进入启动流程,MySQL首次初始化通常需要20秒到1分钟不等。
使用docker compose ps可以查看容器状态,如果STATUS列显示Up且没有重启标志,说明运行正常。使用docker compose logs mysql查看详细日志,当看到ready for connections字样时,表示MySQL已经准备好接受客户端连接。此时可以在宿主机上用mysql客户端或数据库管理工具连接localhost:3306进行验证。
docker compose up -d docker compose ps docker compose logs -f mysql docker exec -it mysql8 mysql -uroot -proot123456 -e "SHOW DATABASES;"
如果容器启动后立即退出,优先检查日志中的报错信息。常见问题包括宿主机3306端口被占用、宿主机mysql-data目录权限不足、环境变量中密码包含特殊字符导致shell解析错误等。权限问题通常表现为日志中出现permission denied错误,可以通过给目录授予写权限解决,执行chmod -R 777 ./mysql-data可以快速验证,但生产环境应使用更合理的属主设置。
时区问题也经常被忽略。如果不设置TZ环境变量,容器默认使用UTC时间,通过SQL插入的NOW()函数返回值会比北京时间慢8个小时。在environment中加入TZ: Asia/Shanghai可以解决这个问题。另外command参数中指定的字符集配置能确保表和连接默认使用utf8mb4,避免中文乱码和emoji存储失败的问题。
健康检查配置让Compose能够感知MySQL的真实就绪状态。mysqladmin ping命令只有在数据库完全启动后才返回成功,相比仅仅检查端口是否开放要可靠得多。在编写依赖MySQL的其他服务时,健康检查状态可以作为启动顺序的判断依据。对于不需要健康检查的场景,也可以直接省略healthcheck配置块。
备份与恢复策略
容器化部署的MySQL同样需要定期备份,不能因为数据在卷里就掉以轻心。最简单的备份方式是使用容器内的mysqldump工具导出SQL文件。
docker exec mysql8 mysqldump -uroot -proot123456 --databases app_db > backup_$(date +%F).sql
恢复数据时,把SQL文件重新导入到容器中即可。如果恢复的目标是一个全新的数据库实例,需要先确保数据库已经创建,或者直接使用初始化脚本机制让容器在首次启动时自动导入。
docker exec -i mysql8 mysql -uroot -proot123456 app_db < backup_2024-01-15.sql
对于数据量较大的场景,直接复制数据目录也是一种选择,但必须停止容器后再操作,避免文件正在写入时复制导致数据不一致。执行docker compose stop后,把宿主机上的mysql-data目录整个打包,就得到了一份物理备份。恢复时将这个目录解压到目标位置,启动新容器即可。
整套方案从Compose文件编写到备份恢复已经形成了闭环。对于开发测试环境来说,这个配置足以应对绝大多数场景。如果还需要进一步扩展,可以考虑加入phpMyAdmin或Adminer等数据库管理工具,把它们作为额外服务写进同一个Compose文件中,与MySQL容器通过内部网络通信。

