Docker Compose 部署 MySQL:Linux 生产环境最佳实践

发布时间:2026/7/17 6:26:10
Docker Compose 部署 MySQL:Linux 生产环境最佳实践 1. 为什么用 Docker Compose 部署 MySQL 是 Linux 环境下的“标准答案”在 Linux 上部署 MySQL你其实只有三条路可走源码编译、包管理器安装apt/yum/dnf、容器化部署。前两条路我踩过太多坑——编译一次耗掉两小时还可能因 OpenSSL 版本不兼容直接失败用 apt install mysql-server 装出来的版本常被锁死在 8.0.33 这种老稳定版想开 GTID 或启用密码强度插件得自己编译插件再改一堆 SELinux 上下文。而 Docker Compose它不是“另一种选择”它是把 MySQL 当成一个可声明、可版本化、可快照、可迁移的服务单元来对待的现代实践方式。你搜“linux mysql 安装教程”前二十页几乎全是手敲 systemctl enable、修改 /etc/mysql/my.cnf、chown -R mysql:mysql /var/lib/mysql……这些操作在单机开发环境尚可在测试环境就容易出错在 CI/CD 流水线里更是灾难——因为没人能保证你下次重装系统时那个 chmod 755 的配置目录权限和上次一模一样。Docker Compose 把所有这些“状态”全部收束进一个compose.yaml文件里端口映射是哪几个、root 密码是多少、数据存到宿主机哪个路径、初始化 SQL 脚本放哪儿、是否自动重启、日志轮转策略怎么配……全写死、可 Git 管理、可 diff、可回滚。这不是偷懒是把运维动作从“手艺活”升级为“工程活”。更关键的是它天然解决了一个长期被忽视的痛点环境一致性断裂。你在 Ubuntu 22.04 上用 apt 装的 MySQL和同事在 CentOS 7 上装的哪怕都是 8.0.33底层 glibc 版本、libaio 行为、甚至默认排序规则collation都可能不同。而mysql:8.4.9-oraclelinux9这个镜像无论你跑在 Ubuntu、Debian、Rocky Linux 还是 WSL2 里启动出来的 MySQL 实例其二进制行为、默认参数、字符集支持完全一致。这才是“Linux 上部署 MySQL”的终极目标不是“让它跑起来”而是“让它在任何符合规范的 Linux 环境里以完全相同的方式跑起来”。所以当你看到热搜词里反复出现 “docker compose restart always”、“设置 volumes”、“mysql 设置唯一已经有重复数据库”它们背后指向的其实是同一个问题如何让数据库服务具备生产级的健壮性、可复现性和隔离性。而 Docker Compose就是这个答案的基础设施层。2. 核心设计逻辑为什么是 Compose 而不是裸 docker run很多人会问docker run -d --name mysql -e MYSQL_ROOT_PASSWORD123 -p 3306:3306 mysql:8.4一行命令就能跑起来干嘛还要多此一举写 YAML这个问题的答案藏在三个被低估的现实约束里服务依赖链、配置可维护性、生命周期协同。先说服务依赖链。你不可能只跑一个 MySQL。真实项目里它永远和应用服务比如 Python Flask 后端、缓存Redis、消息队列RabbitMQ绑在一起。用docker run启动十个容器光是记住哪个容器名对应哪个服务、哪个网络要连、哪个卷要挂载就足以让人崩溃。而 Compose 的depends_on不是简单“启动顺序”它是通过定义服务拓扑让docker compose up自动构建一个专用桥接网络默认叫projectname_default所有服务在这个网络内用服务名互访——你的 Flask 应用代码里写host: db, port: 3306就行不用记 IP也不用--link这种已废弃的参数。这解决了“服务发现”的原始问题。再说配置可维护性。docker run命令行参数超过 15 个就难以阅读和复用。而 YAML 是结构化配置的黄金标准。举个实际例子MySQL 默认最大连接数max_connections151线上业务常需调到 500。用docker run你得加--sysctl net.core.somaxconn1024 --ulimit nofile65536:65536还得在命令行末尾塞--max-connections500。一旦漏掉一个--ulimitMySQL 启动时就会报Cant create thread (errno 11)。而 Compose 中这些全在deploy.resources和command下清晰分层services: db: image: mysql:8.4.9 command: --max-connections500 --innodb-buffer-pool-size1G deploy: resources: limits: memory: 2G pids: 512 reservations: memory: 1G sysctls: - net.core.somaxconn1024最后是生命周期协同。docker run启动的容器docker stop时是发 SIGTERM但 MySQL 官方镜像的 entrypoint 脚本默认会等 30 秒才强制 kill。而 Compose 的restart: always不是简单重启它结合了健康检查healthcheck你可以定义mysqladmin ping -h localhost -u root -p$$MYSQL_ROOT_PASSWORD作为探针当 MySQL 进程僵死但容器还在运行时Compose 会主动 kill 并重建容器而不是让一个“假活”的数据库挂着。这才是restart always在生产环境真正该有的含义——不是“崩溃后重启”而是“失联后自愈”。提示很多新手误以为restart: always能解决 MySQL 初始化失败的问题。实际上如果MYSQL_DATABASE指定的库名已存在或初始化 SQL 有语法错误容器会反复 crash-loop。此时必须配合healthcheck和depends_on.condition: service_healthy否则上游服务会因连接超时而雪崩。3. 实操核心从零构建一个生产就绪的 MySQL Compose 部署我们不写“Hello World”式 demo直接上一个经过三套生产环境验证的最小可行配置。它包含四个不可妥协的要素数据持久化、配置外置化、安全加固、可观测性。下面逐项拆解每一步都附带原理说明和避坑点。3.1 数据持久化为什么不能只靠-v /host/path:/var/lib/mysql这是最常被误解的环节。网上教程千篇一律教“-v ./mysql-data:/var/lib/mysql”但如果你真这么干在宿主机上 ls 一下/var/lib/mysql会发现里面全是空目录和 socket 文件真正的数据文件ibdata1、ib_logfile0根本不在那里——它们被 MySQL 的datadir参数指向了/var/lib/mysql下的子目录。而问题在于MySQL 官方镜像的 entrypoint 脚本在首次启动时会检测/var/lib/mysql是否为空。如果非空它会跳过初始化直接启动。这就埋下两个雷第一如果你用docker volume create mysql-data创建卷然后挂载到/var/lib/mysql第一次启动正常但第二次docker compose down up时卷里的数据还在entrypoint 就不再创建 root 用户、不执行初始化 SQL导致你一脸懵“密码怎么不对了”第二更隐蔽的雷是权限。MySQL 进程在容器内以 UID 999mysql 用户运行而宿主机上你创建的./mysql-data目录默认属主是你的当前用户UID 1000。容器启动时entrypoint 会尝试chown -R mysql:mysql /var/lib/mysql但宿主机目录权限若没提前设好就会报Permission denied直接退出。正确解法是“预置权限 显式指定 datadir”。我们放弃./mysql-data这种相对路径改用绝对路径并提前授权# 在宿主机执行Ubuntu/Debian sudo mkdir -p /opt/mysql/data /opt/mysql/conf /opt/mysql/init sudo chown -R 999:999 /opt/mysql sudo chmod -R 755 /opt/mysql然后在compose.yaml中明确挂载services: db: image: mysql:8.4.9 volumes: - /opt/mysql/data:/var/lib/mysql:rw - /opt/mysql/conf:/etc/mysql/conf.d:ro - /opt/mysql/init:/docker-entrypoint-initdb.d:ro注意:ro只读标记——初始化脚本和配置文件绝不能被容器内进程修改这是安全基线。3.2 配置外置化用 conf.d 覆盖而非硬编码 commandMySQL 官方镜像支持两种配置方式命令行参数command:和配置文件/etc/mysql/conf.d/*.cnf。前者适合临时调试后者才是生产首选。因为配置文件可以被 Git 管理、可以做 A/B 测试、可以按环境拆分dev.cnf / prod.cnf而命令行参数一旦写死改一个参数就得 rebuild 镜像。在/opt/mysql/conf/my.cnf中写入[mysqld] # 强制 UTF8MB4 全局默认 character-set-server utf8mb4 collation-server utf8mb4_unicode_ci skip-character-set-client-handshake # 性能与安全 max_connections 500 innodb_buffer_pool_size 1G innodb_log_file_size 256M default_authentication_plugin mysql_native_password # 日志与审计 log_error /var/log/mysql/error.log slow_query_log ON slow_query_log_file /var/log/mysql/slow.log long_query_time 2 # 网络 bind-address 0.0.0.0 port 3306这里有个关键细节skip-character-set-client-handshake。它强制客户端连接时即使声明了charsetlatin1MySQL 也无视它统一用utf8mb4。这是解决“MySQL 自动忽略大小写”这类问题的底层开关——大小写敏感性由collation决定而utf8mb4_unicode_ci是大小写不敏感的utf8mb4_bin才是敏感的。如果你的应用需要区分大小写比如用户登录名就把collation-server改成utf8mb4_bin。3.3 安全加固环境变量只是起点Secrets 才是终点MYSQL_ROOT_PASSWORD123456明文写在 YAML 里这是初级错误。Docker Compose 支持.env文件和 Secrets 两种机制。.env文件虽比明文好但仍在 Git 里可见Secrets 则通过内存文件系统/run/secrets/注入且只对指定服务可见。第一步创建 secrets 文件echo my-super-secret-root-pass | docker secret create mysql_root_password - echo my-app-db-name | docker secret create mysql_database_name - echo my-app-user | docker secret create mysql_user - echo my-app-user-pass | docker secret create mysql_user_password -第二步在compose.yaml中引用services: db: image: mysql:8.4.9 environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password MYSQL_DATABASE_FILE: /run/secrets/mysql_database_name MYSQL_USER_FILE: /run/secrets/mysql_user MYSQL_PASSWORD_FILE: /run/secrets/mysql_user_password secrets: - mysql_root_password - mysql_database_name - mysql_user - mysql_user_password secrets: mysql_root_password: external: true mysql_database_name: external: true mysql_user: external: true mysql_user_password: external: true注意external: true—— 这表示 secrets 由 Docker daemon 管理而非 Compose 自建。这样做的好处是secrets 可以跨多个 Compose 项目复用且删除项目时不会误删 secrets。3.4 可观测性没有日志和指标的数据库等于黑盒一个健康的 MySQL 必须提供三类输出错误日志error.log、慢查询日志slow.log、性能指标Prometheus Exporter。前两者我们已在my.cnf中开启但日志路径/var/log/mysql/需要挂载出来供宿主机查看volumes: - /opt/mysql/data:/var/lib/mysql:rw - /opt/mysql/conf:/etc/mysql/conf.d:ro - /opt/mysql/init:/docker-entrypoint-initdb.d:ro - /opt/mysql/logs:/var/log/mysql:rw # 新增日志挂载第三类指标我们引入官方推荐的prom/mysqld-exporter。它监听 MySQL 的 performance_schema暴露/metrics端点给 Prometheus 抓取。完整compose.yaml片段如下services: db: image: mysql:8.4.9 # ... 其他配置保持不变 environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password # 创建一个专用用户供 exporter 连接 command: --max-connections500 --innodb-buffer-pool-size1G --log-error/var/log/mysql/error.log --slow-query-log-file/var/log/mysql/slow.log --long-query-time2 exporter: image: prom/mysqld-exporter:v0.15.0 depends_on: - db environment: DATA_SOURCE_NAME: root:$$MYSQL_ROOT_PASSWORD(db:3306)/ volumes: - /opt/mysql/secrets:/run/secrets:ro ports: - 9104:9104这里DATA_SOURCE_NAME用了$$MYSQL_ROOT_PASSWORD双美元符是因为 Compose 会解析单$为环境变量而我们需要把字面量$MYSQL_ROOT_PASSWORD传给 exporter 容器。Exporter 启动后访问http://localhost:9104/metrics就能看到mysql_global_status_threads_connected这类指标。4. 关键实操步骤与参数详解从部署到验证的全流程现在把上面所有设计落地为可执行的 7 步操作。每一步都标注了“为什么这么做”和“不这么做会怎样”避免你成为只会复制粘贴的脚本工程师。4.1 第一步准备宿主机环境Ubuntu 22.04 示例# 1. 更新系统并安装基础工具 sudo apt update sudo apt upgrade -y sudo apt install -y curl gnupg2 software-properties-common # 2. 安装 Docker确保版本 24.0.0 curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER # 重启终端或执行 newgrp docker 生效 # 3. 安装 Docker Composev2.24.7与 Docker Desktop 绑定的 v1 已弃用 sudo apt install -y docker-compose-plugin # 验证docker compose version注意不要用pip install docker-compose它安装的是已废弃的 Compose v1命令是docker-compose带横线而新版是docker compose无横线。docker compose ps和docker-compose ps是两个不同二进制混用会导致no configuration file provided: not found错误——因为 v1 默认找docker-compose.ymlv2 默认找compose.yaml。4.2 第二步创建项目目录结构与权限mkdir -p ~/mysql-prod/{data,conf,init,logs,secrets} # 创建 secrets 目录用于存放密码文件后续用 touch ~/mysql-prod/secrets/.gitkeep # 关键设置 UID 999 权限MySQL 容器内用户 ID sudo chown -R 999:999 ~/mysql-prod sudo chmod -R 755 ~/mysql-prod实操心得chown -R 999:999必须在docker compose up之前执行。我曾因忘记这步在docker logs mysql-db里看到chown: changing ownership of /var/lib/mysql: Permission denied查了三小时才发现是宿主机目录属主不对。MySQL 镜像的 entrypoint 脚本会在启动时尝试chown -R mysql:mysql /var/lib/mysql如果宿主机目录属主不是 999它就失败。4.3 第三步编写 compose.yaml生产精简版将以下内容保存为~/mysql-prod/compose.yamlversion: 3.8 services: db: image: mysql:8.4.9-oraclelinux9 container_name: mysql-db restart: unless-stopped environment: MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password MYSQL_DATABASE_FILE: /run/secrets/mysql_database_name MYSQL_USER_FILE: /run/secrets/mysql_user MYSQL_PASSWORD_FILE: /run/secrets/mysql_user_password TZ: Asia/Shanghai secrets: - mysql_root_password - mysql_database_name - mysql_user - mysql_user_password command: --max-connections500 --innodb-buffer-pool-size1G --innodb-log-file-size256M --log-error/var/log/mysql/error.log --slow-query-log-file/var/log/mysql/slow.log --long-query-time2 --bind-address0.0.0.0 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -u, root, --password$$MYSQL_ROOT_PASSWORD] timeout: 20s retries: 10 start_period: 40s volumes: - /home/$USER/mysql-prod/data:/var/lib/mysql:rw - /home/$USER/mysql-prod/conf:/etc/mysql/conf.d:ro - /home/$USER/mysql-prod/init:/docker-entrypoint-initdb.d:ro - /home/$USER/mysql-prod/logs:/var/log/mysql:rw ports: - 3306:3306 networks: - mysql-net exporter: image: prom/mysqld-exporter:v0.15.0 container_name: mysql-exporter depends_on: db: condition: service_healthy environment: DATA_SOURCE_NAME: root:$$MYSQL_ROOT_PASSWORD(db:3306)/ volumes: - /home/$USER/mysql-prod/secrets:/run/secrets:ro ports: - 9104:9104 networks: - mysql-net networks: mysql-net: driver: bridge secrets: mysql_root_password: file: /home/$USER/mysql-prod/secrets/mysql_root_password.txt mysql_database_name: file: /home/$USER/mysql-prod/secrets/mysql_database_name.txt mysql_user: file: /home/$USER/mysql-prod/secrets/mysql_user.txt mysql_user_password: file: /home/$USER/mysql-prod/secrets/mysql_user_password.txt参数详解restart: unless-stopped比always更合理。它允许你手动docker stop mysql-db后不自动重启方便维护。start_period: 40sMySQL 初始化可能长达 30 秒尤其数据量大时健康检查必须等够时间再开始探针否则会误判为失败。TZ: Asia/Shanghai避免时间戳混乱。MySQL 的NOW()函数依赖容器时区不设会导致日志时间与宿主机差 8 小时。4.4 第四步生成 Secrets 文件并启动# 创建 secrets 目录如果不存在 mkdir -p ~/mysql-prod/secrets # 生成密码文件用 openssl 生成强密码 openssl rand -base64 24 | tee ~/mysql-prod/secrets/mysql_root_password.txt echo myapp_db | tee ~/mysql-prod/secrets/mysql_database_name.txt echo appuser | tee ~/mysql-prod/secrets/mysql_user.txt openssl rand -base64 24 | tee ~/mysql-prod/secrets/mysql_user_password.txt # 启动服务-d 后台运行 cd ~/mysql-prod docker compose up -d # 查看状态 docker compose ps # 输出应为mysql-db running (healthy) mysql-exporter running常见问题排查如果docker compose ps显示unhealthy立刻执行docker logs mysql-db。90% 的情况是密码文件路径错误或权限不对。检查secrets.file路径是否为绝对路径且~/已被 shell 展开为/home/username/。4.5 第五步验证数据库功能与初始化脚本假设你想在数据库创建时自动建表。在~/mysql-prod/init/01-create-tables.sql中写入-- 创建用户表UTF8MB4 支持 emoji CREATE TABLE IF NOT EXISTS users ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_unicode_ci; -- 插入测试数据 INSERT INTO users (username, email) VALUES (admin, adminexample.com) ON DUPLICATE KEY UPDATE updated_at CURRENT_TIMESTAMP;注意SQL 文件名必须以数字开头如01-xxx.sql因为 entrypoint 按字母序执行。如果写成create.sql它会在01-xxx.sql之前执行可能导致表不存在就插入数据而报错。4.6 第六步连接测试与字符集验证用 MySQL 客户端连接# 安装客户端Ubuntu sudo apt install -y mysql-client # 连接本地 3306 端口 mysql -h 127.0.0.1 -P 3306 -u appuser -p # 输入 ~/mysql-prod/secrets/mysql_user_password.txt 中的密码 # 在 MySQL 提示符下执行 SHOW VARIABLES LIKE character_set%; SHOW VARIABLES LIKE collation%; SELECT DATABASE(), USER();预期输出中character_set_server和collation_server必须是utf8mb4和utf8mb4_unicode_ci。如果还是latin1说明my.cnf没生效——检查/opt/mysql/conf/下的文件是否真的被挂载进容器docker exec mysql-db ls /etc/mysql/conf.d/。4.7 第七步日志与监控接入查看错误日志tail -f ~/mysql-prod/logs/error.log # 正常启动应看到类似mysqld: ready for connections.查看慢查询日志# 手动触发一个慢查询在 MySQL 客户端中 SELECT SLEEP(3); # 然后检查 tail -f ~/mysql-prod/logs/slow.log # 应看到记录# Time: 2024-06-15T08:12:34.567890Z\n# UserHost: appuser[appuser] localhost []\n# Query_time: 3.000123 ...验证 Prometheus Exportercurl http://localhost:9104/metrics | grep mysql_global_status_uptime # 应返回mysql_global_status_uptime 12345表示 MySQL 已运行秒数5. 常见问题与独家排查技巧实录在上百次部署中我总结出 7 类高频问题。它们不像文档里写的那么“标准”而是真实世界里让你抓耳挠腮的细节。下面按发生频率排序并给出可立即执行的诊断命令。5.1 问题docker compose up后容器立即退出docker logs mysql-db显示Permission denied根因分析宿主机目录/home/username/mysql-prod/data的属主不是 UID 999或 SELinux/AppArmor 阻止了挂载。一键诊断# 检查宿主机目录权限 ls -ld ~/mysql-prod/data # 正确输出drwxr-xr-x 2 999 999 4096 Jun 15 10:00 /home/username/mysql-prod/data # 如果显示 1000:1000修复命令 sudo chown -R 999:999 ~/mysql-prod/data # 检查 AppArmorUbuntu 默认启用 sudo aa-status | grep docker # 如果有 docker-* profile临时禁用测试 sudo systemctl stop apparmor sudo systemctl start docker独家技巧在compose.yaml的db服务下加一行user: 999:999强制容器以 UID 999 运行绕过 entrypoint 的 chown 步骤。但这只是权宜之计长期仍需修复宿主机权限。5.2 问题docker compose ps显示unhealthy但docker logs mysql-db没报错根因分析健康检查探针mysqladmin ping失败但 MySQL 进程其实活着。常见于MYSQL_ROOT_PASSWORD未正确注入或密码含特殊字符如$,!被 shell 解析。一键诊断# 进入容器内部手动执行探针命令 docker exec -it mysql-db bash # 在容器内执行注意$$MYSQL_ROOT_PASSWORD 在容器内是字面量需用实际值 mysqladmin ping -h localhost -u root -pyour_actual_password # 如果报错 Access denied说明密码不对如果报错 Cant connect说明 MySQL 没监听。 # 检查密码是否被解析在宿主机执行 echo $MYSQL_ROOT_PASSWORD # 如果输出为空说明 .env 文件没加载或变量名错独家技巧在healthcheck.test中把密码写死为一个测试值仅调试用test: [CMD, mysqladmin, ping, -h, localhost, -u, root, -pmy-test-pass]然后在environment中加MYSQL_ROOT_PASSWORD: my-test-pass。确认健康检查通了再换回 secrets。5.3 问题应用连接 MySQL 报Access denied for user appuser172.20.0.3根因分析MySQL 默认只允许localhost用户连接而 Docker 网络中应用容器 IP 是172.20.0.x属于远程连接需要显式授权。一键诊断# 进入 MySQL 容器 docker exec -it mysql-db mysql -u root -p # 输入 root 密码后执行 SELECT host, user FROM mysql.user; # 如果 appuser 的 host 是 localhost就无法从其他容器连接。解决方案在~/mysql-prod/init/02-grant-privileges.sql中添加-- 授权 appuser 从任意 IP 连接生产环境建议限定网段 CREATE USER IF NOT EXISTS appuser% IDENTIFIED BY your_password_here; GRANT ALL PRIVILEGES ON myapp_db.* TO appuser%; FLUSH PRIVILEGES;注意appuser%中的%是通配符表示任意主机。生产环境应改为appuser172.20.0.%限定在 Docker 网络内。5.4 问题docker compose down后再up数据库数据“消失”了根因分析docker compose down默认删除匿名卷anonymous volumes而volumes:中未指定卷名时Compose 会创建匿名卷。你的/var/lib/mysql数据实际存在匿名卷里down后就被清空。一键诊断# 查看所有卷 docker volume ls # 如果看到类似 mysql-prod_db_data 的命名卷说明配置正确如果只有 random-hash就是匿名卷。 # 查看容器挂载详情 docker inspect mysql-db | jq .[0].Mounts # 如果 Source 是 /var/lib/docker/volumes/random-hash/_data就是匿名卷。解决方案在compose.yaml中显式声明命名卷volumes: mysql-data: driver: local driver_opts: type: none device: /home/$USER/mysql-prod/data o: bind services: db: volumes: - mysql-data:/var/lib/mysql:rw5.5 问题docker compose restart always不生效容器 crash 后没重启根因分析restart策略只对容器进程退出生效而 MySQL 官方镜像的 entrypoint 脚本在初始化失败时会以 exit code 1 退出但 Compose 认为这是“预期退出”不触发重启。一键诊断# 查看容器退出原因 docker inspect mysql-db | jq .[0].State.Status, .[0].State.ExitCode, .[0].State.Error # 如果 ExitCode 是 1且 Error 是 Database is uninitialized and superuser password is not specified说明环境变量缺失。 # 查看最近 10 次重启记录 docker events --since 24h --filter containermysql-db --filter eventdie --format {{.Actor.Attributes.exitCode}}解决方案用restart: on-failure:5替代always并配合健康检查restart: on-failure:5 healthcheck: test: [CMD, mysqladmin, ping, -h, localhost, -u, root, --password$$MYSQL_ROOT_PASSWORD] interval: 30s timeout: 10s retries: 3 start_period: 60s5.6 问题docker compose ps no configuration file provided: not found根因分析Compos v2 默认找compose.yaml而你文件名是docker-compose.yml或docker-compose.yaml。一键诊断# 查看当前目录下文件 ls -la | grep -E (compose|docker-compose) # 正确文件名必须是compose.yaml 或 docker-compose.yaml注意是 .yaml不是 .yml # 强制指定文件名启动 docker compose -f docker-compose.yaml up -d独家技巧创建软链接统一入口ln -sf compose.yaml docker-compose.yaml5.7 问题MySQL 启动后SELECT NOW()返回时间比宿主机快/慢 8 小时根因分析容器内时区未同步MySQL 的NOW()函数依赖系统时区而官方镜像默认是 UTC。解决方案在compose.yaml的db服务下加environment: TZ: Asia/Shanghai volumes: - /etc/localtime:/etc/localtime:ro注意/etc/localtime是宿主机的时区文件ro只读挂载。这样容器内date命令和 MySQL 的NOW()就与宿主机完全一致。6. 进阶扩展从单机部署到高可用集群的平滑演进当你的应用用户量突破 10 万单节点 MySQL 必然成为瓶颈。此时无需推倒重来只需在现有 Compose 架构上叠加两层读写分离和备份容灾。整个过程不改动一行应用代码只调整compose.yaml。6.1 读写分离主从复制 ProxySQL 路由在compose.yaml中新增master和slave服务并用ProxySQL做流量分发services: master: image: mysql:8.4.9 # ... 配置同原 db但加 binlog 参数 command: --log-binmysql-bin --server-id1 --binlog-formatROW slave: image: mysql:8.4.9 # ... 配置同 master但 server-id2 command: --server-id2 --relay-logrelay-bin --read_onlyON proxysql: image: perconalab/proxysql:2.4.4 volumes: - ./proxysql.cnf:/etc/proxysql.cnf:ro ports: - 6033:6033 # 应用连接此端口 - 6032:6032 # ProxySQL 管理端口proxysql.cnf中定义路由规则mysql_servers ( { addressmaster, port3306, hostgroup0, max_replication_lag10 }, { addressslave, port3306, hostgroup1, max_replication_lag10 } ) mysql_query_rules ( { rule_id1, active1, match_digest^SELECT.*FOR UPDATE$, destination_hostgroup0, apply1 }, { rule_id2, active1, match_digest^SELECT