用 Compose 部署一个可上生产基线的 MySQL:数据/配置/日志/备份目录分离并持久化,端口只绑回环,root 密码走 .env(chmod 600),带健康检查、资源限额、utf8mb4、ROW 格式 binlog、慢查询日志。

示例锁定 mysql:8.0(新项目首选)。文末附 5.7 的差异说明。

一、目录结构

/opt/mysql/
├── docker-compose.yml
├── .env                 # 600,root/业务密码
└── mysql/
    ├── conf/my.cnf
    ├── data/            # 数据目录,chown 999:999
    ├── logs/            # chown 999:999
    └── backup/          # 备份输出
sudo mkdir -p /opt/mysql/mysql/{conf,data,logs,backup}
sudo chown -R 999:999 /opt/mysql/mysql/{data,logs}

MySQL 官方镜像内 mysql 用户 uid 固定为 999,不预 chown 会因权限不足启动失败。

二、.env

umask 077
cat > /opt/mysql/.env <<'EOF'
MYSQL_ROOT_PASSWORD=替换为强密码
MYSQL_DATABASE=appdb
MYSQL_USER=app
MYSQL_PASSWORD=替换为强密码
TZ=Asia/Shanghai
EOF
chmod 600 /opt/mysql/.env

强密码生成:openssl rand -base64 24 | tr -d '/+='。

三、docker-compose.yml

name: mysql-stack

x-logging: &default-logging
  driver: json-file
  options:
    max-size: "50m"
    max-file: "5"

services:
  mysql:
    image: mysql:8.0.40
    container_name: mysql8
    restart: unless-stopped
    logging: *default-logging
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
      TZ: ${TZ}
    ports:
      # 只绑回环;远程访问走 SSH 隧道,不对公网暴露 3306
      - "127.0.0.1:3306:3306"
    volumes:
      - ./mysql/data:/var/lib/mysql
      - ./mysql/conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro
      - ./mysql/logs:/var/log/mysql
      - ./mysql/backup:/backup
    security_opt:
      - no-new-privileges:true
    mem_limit: 2g
    mem_reservation: 1g
    healthcheck:
      test: ["CMD-SHELL", "mysqladmin ping -h 127.0.0.1 -uroot -p\"$$MYSQL_ROOT_PASSWORD\" --silent"]
      interval: 10s
      timeout: 5s
      retries: 6
      start_period: 30s

注意:compose 里引用容器自身的环境变量要写 $$MYSQL_ROOT_PASSWORD(双美元符号转义)。

四、my.cnf

mysql/conf/my.cnf(按 2G 容器限额的 4C 主机示例):

[mysqld]
# ---------- 基础 ----------
character-set-server        = utf8mb4
collation-server            = utf8mb4_unicode_ci
default-storage-engine      = InnoDB
default-time-zone           = '+08:00'
skip-name-resolve           = 1
# lower_case_table_names 必须在首次初始化前设定;8.0 初始化后不可改,默认 0

# ---------- 连接 ----------
max_connections             = 500
max_allowed_packet          = 64M
wait_timeout                = 28800
interactive_timeout         = 28800
table_open_cache            = 2000
open_files_limit            = 65535

# ---------- InnoDB(buffer pool 约为容器内存的 50%)----------
innodb_buffer_pool_size     = 1G
innodb_buffer_pool_instances= 2
innodb_redo_log_capacity    = 256M
innodb_flush_log_at_trx_commit = 1
innodb_flush_method         = O_DIRECT
innodb_file_per_table       = 1
innodb_io_capacity          = 1000
innodb_io_capacity_max      = 2000

# ---------- 日志 ----------
log_error                   = /var/log/mysql/error.log
slow_query_log              = 1
slow_query_log_file         = /var/log/mysql/slow.log
long_query_time             = 2
log_queries_not_using_indexes = 1

# ---------- Binlog(备份恢复/主从基础)----------
server_id                   = 1
log_bin                     = mysql-bin
binlog_format               = ROW
binlog_expire_logs_seconds  = 604800   # 7 天(5.7 用 expire_logs_days=7)

# ---------- 严格模式 ----------
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

[client]
default-character-set       = utf8mb4

[mysql]
default-character-set       = utf8mb4

五、启动与验证

cd /opt/mysql
docker compose config -q
docker compose up -d
# 首次初始化要 20~40 秒,等待 healthy
docker compose ps
docker compose logs -f mysql

验证字符集、端口、连通性:

docker exec mysql8 mysql -uroot -p"$(grep MYSQL_ROOT_PASSWORD .env | cut -d= -f2)" \
  -e "show variables like 'character_set_server'; select version();"

ss -ltnp | grep 3306        # 应为 127.0.0.1:3306,而不是 0.0.0.0

远程连接一律走 SSH 隧道(在你自己的电脑执行):

ssh -L 3306:127.0.0.1:3306 user@203.0.113.10
# 然后本地客户端连 127.0.0.1:3306

六、日常运维与备份

docker compose ps
docker compose restart mysql
docker compose pull && docker compose up -d     # 小版本升级(先备份!)

# 手动全量备份(一致性快照,不锁表)
docker exec mysql8 sh -c \
  'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --routines --events \
   --all-databases | gzip > /backup/all-$(date +%F).sql.gz'

# 恢复
gunzip < /backup/all-2026-09-20.sql.gz | docker exec -i mysql8 mysql -uroot -p"密码"

加一条宿主机 crontab,每天 2:30 备份并保留 14 天:

30 2 * * * cd /opt/mysql && docker exec mysql8 sh -c 'mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --routines --events --all-databases | gzip > /backup/all-$(date +\%F).sql.gz' && find /opt/mysql/mysql/backup -name 'all-*.sql.gz' -mtime +14 -delete

备份文件应再同步到对象存储或另一台机器,数据目录和备份放在同一块盘不等于有备份。

七、MySQL 5.7 的差异

如果历史系统必须用 5.7(5.7 官方已 EOL,新项目不要选):

  • 镜像换 mysql:5.7.44,容器名按需改;
  • binlog 过期用 expire_logs_days = 7,没有 innodb_redo_log_capacity,用 innodb_log_file_size = 256M;
  • 如需保持旧客户端兼容,启动命令加 --default-authentication-plugin=mysql_native_password。

八、常见问题

现象 原因与处理
容器反复重启,日志 Permission denied / Can't create/write to file data 或 logs 目录属主不对,chown -R 999:999
Expression #1 of ORDER BY is not in GROUP BY 严格 sql_mode=ONLY_FULL_GROUP_BY 生效;正确做法是修 SQL,不建议全局关严格模式
健康检查一直 starting 首次初始化较慢,start_period 内属正常;超期看 error.log,常见于内存不足或 data 目录是从其它大版本直接挂来
外部连不上 3306 设计如此(只绑回环)。确需开放请改用云安全组限来源 IP,并给业务账号限定 host,不要对 0.0.0.0 开放
改了 my.cnf 不生效 docker compose restart mysql;启动参数(command)才需要 up -d 重建
升级大版本(5.7→8.0)启动失败 大版本不能直接换镜像挂旧数据目录,应先用 mysqldump 逻辑迁移到全新初始化的 8.0
磁盘告警 binlog 已设 7 天自动清理;另确认 json-file 日志轮转生效,慢日志定期归档
最后修改:2026 年 09 月 20 日
如果觉得我的文章对你有用,请随意赞赏