言語を切り替える
テーマを切り替える

DockerでMySQLをデプロイ:データ永続化から主従レプリケーションまでの完全ガイド

ターミナルに刺さるようなエラー——半月分のテストデータがすべて消えました。午後に MySQL コンテナを再起動しただけ。普通の再起動だと思っていたのに、データまで一緒に消えてしまいました。

Docker で MySQL を動かすのは、docker run -e MYSQL_ROOT_PASSWORD=123456 mysql 一行で済むように見えます。しかしコンテナ再起動でデータが消える、設定ファイルのマウントが効かない、ローカルアプリから接続できない、本番向けの主従レプリケーション——踏んで初めて分かる落とし穴がたくさんあります。本記事ではデータ永続化から主従レプリケーションまで、各設定を順を追って説明します。

Docker MySQL 単体デプロイの基礎

いちばん簡単な起動方法(おすすめしない理由)

よくある誤解から。初めて Docker で MySQL を入れる人は、こうすることが多いです。

docker run --name mysql-test -e MYSQL_ROOT_PASSWORD=123456 -d mysql:8.0

起動はします。コンテナも正常。中に入って DB 操作もできて、一見完璧です。しかしこれは時限爆弾です。

なぜか。コンテナは本質的に一時的です。削除したり、うっかり再起動したりすると、データはすべて消えます。MySQL はデフォルトで /var/lib/mysql にデータを置きますが、これはコンテナ内です。コンテナが消えればディレクトリも消えます。

初めてこの罠にハマったときは、MySQL のバグだと思いました。のちに分かったのは、MySQL ではなく、永続化をしていなかったからです。

データ永続化:Volume マウントの正しいやり方

要するに、MySQL のデータディレクトリをホスト側にマッピングし、コンテナが消えてもデータを残すことです。

Docker には 3 種類のマウントがあります。

  1. bind mount:ホストのディレクトリを直接マップ(例:/home/mysql/data
  2. named volume:Docker が管理するボリューム。実体の場所を意識しなくてよい
  3. tmpfs:メモリ上。再起動で消える。ほぼ使わない

2024 年時点では Docker 公式も named volume を推奨しており、bind mount と性能差はほぼありません。開発は named volume、本番はバックアップしやすい bind mount、という使い分けが多いです。

完全なコマンド例:

docker run --name mysql-persistent \
  -e MYSQL_ROOT_PASSWORD=rootpwd123 \
  -p 3306:3306 \
  -v mysql-data:/var/lib/mysql \
  -d mysql:8.0

-v mysql-data:/var/lib/mysql が肝です。mysql-data はボリューム名(自動作成され)、/var/lib/mysql はコンテナ内のデータディレクトリです。

永続化の確認:

# コンテナ内で DB を作成
docker exec -it mysql-persistent mysql -uroot -prootpwd123 -e "CREATE DATABASE testdb;"

# コンテナを停止して削除
docker stop mysql-persistent
docker rm mysql-persistent

# 同じ Volume で再起動
docker run --name mysql-persistent \
  -e MYSQL_ROOT_PASSWORD=rootpwd123 \
  -p 3306:3306 \
  -v mysql-data:/var/lib/mysql \
  -d mysql:8.0

# testdb が残っているか確認
docker exec -it mysql-persistent mysql -uroot -prootpwd123 -e "SHOW DATABASES;"

testdb が残っていれば、データは守れています。安心感が違います。

設定ファイルのマウント:MySQL パラメータのカスタマイズ

永続化の次は、MySQL の設定変更です。

文字セットを utf8mb4 にしたい、最大接続数を上げたい——設定ファイルをマウントしないと、コンテナに入って毎回直し、再起動のたびにやり直しになります。

MySQL は /etc/mysql/conf.d/ 以下の設定を読みます。ホスト側の my.cnf をここにマウントします。

ホストで設定ファイルを用意:

mkdir -p /home/mysql/conf
cat > /home/mysql/conf/my.cnf << 'EOF'
[mysqld]
# 文字セット
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

# 接続数
max_connections=1000

# 認証プラグイン(一部クライアントの接続問題対策)
default_authentication_plugin=mysql_native_password

[client]
default-character-set=utf8mb4
EOF

起動時にマウント:

docker run --name mysql-custom \
  -e MYSQL_ROOT_PASSWORD=rootpwd123 \
  -p 3306:3306 \
  -v /home/mysql/conf:/etc/mysql/conf.d \
  -v /home/mysql/data:/var/lib/mysql \
  -d mysql:8.0

設定用とデータ用の 2 つを同時にマウントしています。

反映確認:

docker exec -it mysql-custom mysql -uroot -prootpwd123 -e "SHOW VARIABLES LIKE 'character%';"

character_set_serverutf8mb4 なら OK です。

外部からの接続問題の解決

コンテナは動き、データも永続化し、設定もマウントした。ローカルアプリから接続すると——

ERROR 2003 (HY000): Can't connect to MySQL server on 'localhost' (Connection refused)

または

ERROR 1045 (28000): Access denied for user 'root'@'172.17.0.1'

どちらも経験あります。原因を突き止めるまで時間がかかりました。

問題 1:Connection refused

多くはポートマッピング不足です。-p 3306:3306 でコンテナの 3306 をホストに公開してください。

ホストの 3306 が既に使われている(ローカル MySQL など)場合は別ポートにします。

-p 3307:3306  # ホストは 3307、コンテナ内は 3306

Linux サーバーではファイアウォールも確認します。

# CentOS/RHEL
sudo firewall-cmd --zone=public --add-port=3306/tcp --permanent
sudo firewall-cmd --reload

# Ubuntu
sudo ufw allow 3306/tcp

問題 2:Access denied

権限の問題です。デフォルトの root は localhost からのみ許可されていることがあり、外部 IP からは拒否されます。

root の host を % に変更する例(開発環境向け):

docker exec -it mysql-custom mysql -uroot -prootpwd123
ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'rootpwd123';
GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' WITH GRANT OPTION;
FLUSH PRIVILEGES;

注意点:

  1. mysql_native_password は古いクライアントとの互換性が高い
  2. 本番では root をどこからでも開けない。専用ユーザーを作り IP を制限する

これで外部アプリから接続できるはずです。

ここまでで、Docker MySQL の単体デプロイでよくある大きな穴はほぼ潰せます。

Docker Compose でまとめて管理する

Docker Compose を推す理由

ローカル開発だけなら、先ほどの docker run でも足ります。実務では次のような不満が出てきます。

  1. コマンドが長く、毎回履歴を探す
  2. Volume パスなどパラメータを間違えやすい
  3. チームごとに起動コマンドがバラバラで設定が乱れる
  4. MySQL + Redis + Nginx のように複数コンテナを順に起動するのが面倒

ここで Docker Compose が活きます。

長い docker run を YAML にまとめ、docker-compose up -d で起動、docker-compose down で停止。設定は Git でバージョン管理できます。

新人に「MySQL の起動方法は?」と聞かれたら docker-compose.yml を渡すだけ。git clone して一発起動——説明不要です。

Docker Compose 単体 MySQL 設定

完全な設定例と、項目ごとの説明です。

version: '3.8'

services:
  mysql:
    image: mysql:8.0
    container_name: mysql-standalone
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd123
      MYSQL_DATABASE: myapp
      MYSQL_USER: appuser
      MYSQL_PASSWORD: apppwd123
    ports:
      - "3306:3306"
    volumes:
      - mysql-data:/var/lib/mysql
      - ./conf/my.cnf:/etc/mysql/conf.d/my.cnf
      - ./logs:/var/log/mysql
    networks:
      - mysql-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-prootpwd123"]
      interval: 10s
      timeout: 5s
      retries: 3

volumes:
  mysql-data:

networks:
  mysql-network:
    driver: bridge

environment

  • MYSQL_ROOT_PASSWORD:root パスワード(必須)
  • MYSQL_DATABASE:起動時に自動作成する DB
  • MYSQL_USER / MYSQL_PASSWORD:一般ユーザー(root より安全)

volumes

  • mysql-data:/var/lib/mysql:named volume で永続化
  • ./conf/my.cnf:...:設定ファイル(相対パス)
  • ./logs:/var/log/mysql:ログをホストに出して調査しやすく

restart: always:落ちたら自動再起動。サーバー再起動後もコンテナが上がる

healthcheck:定期的に ping。異常なら再起動の判断材料に

networks:複数コンテナを同じネットワークに入れて名前解決

手順:

  1. ディレクトリ作成:
mkdir -p mysql-docker/{conf,logs}
cd mysql-docker
  1. conf/my.cnf を作成:
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
max_connections=1000
default_authentication_plugin=mysql_native_password

[client]
default-character-set=utf8mb4
  1. 上記の docker-compose.yml を配置

  2. 起動:

docker-compose up -d
  1. 状態確認:
docker-compose ps

出力例:

       Name                     Command                  State                 Ports
------------------------------------------------------------------------------------------------
mysql-standalone      docker-entrypoint.sh mysqld      Up (healthy)    0.0.0.0:3306->3306/tcp

(healthy) は healthcheck 成功の印です。

  1. ログ:
docker-compose logs -f mysql

-ftail -f と同様の追従。起動失敗の原因はだいたいここにあります。

  1. 停止・コンテナ削除:
docker-compose down

コンテナだけ消え、volume(データ)は残ります。データごと消す場合(注意):

docker-compose down -v

ローカル開発は、いまやほぼ Compose 一択です。一度書けば、以降はワンコマンドです。

本番向け:主従レプリケーション

主従レプリケーションの概要

なぜ主従か。

単体 MySQL は小規模なら十分ですが、負荷が増えると限界が来ます。主従は主に次の 2 点を解きます。

  1. 読み書き分離:マスターが書き(INSERT / UPDATE / DELETE)、スレーブが読み(SELECT)。読み多めのアプリではスレーブに分散すると効く
  2. バックアップと可用性:マスター障害時もスレーブで読みは継続しやすい

仕組みはシンプルです。

  • マスターが binlog に変更を記録
  • スレーブがマスターに接続して binlog を取得
  • IO スレッドが relay log に書き、SQL スレッドが relay log を実行
  • マスターの変更がスレーブに反映される

Docker では、server-id を分ける、マスターで binlog を有効化、スレーブでレプリケーションを開始——が核心です。Compose で書いて起動すれば、思ったより難しくありません。

マスターノードの設定

マスターでやることは 3 つ:binlog、server-id、レプリケーション用ユーザー。

  1. conf/master.cnf
[mysqld]
# 一意の server-id(主従で重複不可)
server-id=1

# バイナリログ
log-bin=mysql-bin

# ROW 形式(行単位の変更記録、より安全)
binlog-format=ROW

# 文字セット
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

# 任意:同期対象 DB
# binlog-do-db=myapp

# 任意:同期除外 DB
# binlog-ignore-db=mysql
# binlog-ignore-db=information_schema
  1. マスター用 docker-compose:
version: '3.8'

services:
  mysql-master:
    image: mysql:8.0
    container_name: mysql-master
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd123
      MYSQL_DATABASE: myapp
    ports:
      - "3306:3306"
    volumes:
      - master-data:/var/lib/mysql
      - ./conf/master.cnf:/etc/mysql/conf.d/master.cnf
      - ./logs/master:/var/log/mysql
    networks:
      - mysql-replication

volumes:
  master-data:

networks:
  mysql-replication:
    driver: bridge
  1. マスター起動:
docker-compose up -d mysql-master
  1. レプリケーション用ユーザー:
docker exec -it mysql-master mysql -uroot -prootpwd123
CREATE USER 'repl'@'%' IDENTIFIED WITH mysql_native_password BY 'replpwd123';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';
FLUSH PRIVILEGES;
SHOW MASTER STATUS;

SHOW MASTER STATUS の例:

+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000003 |      156 |              |                  |
+------------------+----------+--------------+------------------+

重要FilePosition をメモし、スレーブ設定で使います。

スレーブノードの設定

  1. conf/slave.cnf
[mysqld]
server-id=2
relay-log=relay-bin
read-only=1
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
  1. docker-compose にスレーブを追加:
version: '3.8'

services:
  mysql-master:
    image: mysql:8.0
    container_name: mysql-master
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd123
      MYSQL_DATABASE: myapp
    ports:
      - "3306:3306"
    volumes:
      - master-data:/var/lib/mysql
      - ./conf/master.cnf:/etc/mysql/conf.d/master.cnf
      - ./logs/master:/var/log/mysql
    networks:
      - mysql-replication

  mysql-slave:
    image: mysql:8.0
    container_name: mysql-slave
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpwd123
    ports:
      - "3307:3306"
    volumes:
      - slave-data:/var/lib/mysql
      - ./conf/slave.cnf:/etc/mysql/conf.d/slave.cnf
      - ./logs/slave:/var/log/mysql
    networks:
      - mysql-replication
    depends_on:
      - mysql-master

volumes:
  master-data:
  slave-data:

networks:
  mysql-replication:
    driver: bridge
  1. スレーブ起動:
docker-compose up -d mysql-slave
  1. スレーブからマスターへ接続:
docker exec -it mysql-slave mysql -uroot -prootpwd123
CHANGE MASTER TO
  MASTER_HOST='mysql-master',
  MASTER_PORT=3306,
  MASTER_USER='repl',
  MASTER_PASSWORD='replpwd123',
  MASTER_LOG_FILE='mysql-bin.000003',
  MASTER_LOG_POS=156;

START SLAVE;
SHOW SLAVE STATUS\G

主従同期の確認

SHOW SLAVE STATUS\G で見る項目:

Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 0
Last_IO_Error:
Last_SQL_Error:

Slave_IO_RunningSlave_SQL_Running がどちらも Yes なら成功です。

動作テスト:

  1. マスターでデータ作成:
docker exec -it mysql-master mysql -uroot -prootpwd123 -e "
USE myapp;
CREATE TABLE test_table (id INT PRIMARY KEY, name VARCHAR(50));
INSERT INTO test_table VALUES (1, 'test data');
"
  1. スレーブで参照:
docker exec -it mysql-slave mysql -uroot -prootpwd123 -e "
USE myapp;
SELECT * FROM test_table;
"

同じ行が見えれば同期 OK です。

よくあるトラブル

問題 1:Slave_IO_Running が No

  • ネットワーク:docker exec -it mysql-slave ping mysql-master
  • repl ユーザーの権限
  • binlog の File / Position の取り違え → SHOW MASTER STATUS で再確認

問題 2:Slave_SQL_Running が No

  • Last_SQL_Error を確認
  • マスターに既存データがある場合は、先にダンプしてスレーブへ投入してからレプリケーション設定

問題 3:Seconds_Behind_Master が大きいまま

  • スレーブの CPU / メモリ / ディスク IO
  • マスターの書き込み負荷 → スレーブを増やす
  • ネットワーク帯域・遅延

リセットしてやり直す例:

STOP SLAVE;
RESET SLAVE;
-- その後 CHANGE MASTER TO と START SLAVE を再実行

性能チューニングとベストプラクティス

Docker MySQL の性能について

Docker が MySQL を遅くするのでは、という不安はよく聞きます。以前はその通りでしたが、2024 年時点ではオーバーヘッドは 5% 以内に収まり、I/O 中心でも体感しにくいことが多いです。

とはいえ、最適化はしておく価値があります。

1. Volume の種類

  • named volume:Docker 管理。開発向け
  • bind mount:ホストパス直マップ。バックアップ・監視しやすく本番向け
volumes:
  - mysql-data:/var/lib/mysql

volumes:
  - /data/mysql:/var/lib/mysql

2. ネットワークモード

bridge で足りない極限ケースでは host モード:

services:
  mysql:
    network_mode: "host"

host では ports マッピングは不要。コンテナがホストの 3306 を直接リッスンします。

3. リソース制限

本番では必須。MySQL がサーバー資源を食い尽くすのを防ぎます。

services:
  mysql:
    image: mysql:8.0
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          memory: 1G

docker-compose --compatibility up、または Swarm モードが必要です。

4. ログローテーション

コンテナログを放置するとディスクが満杯になります。

services:
  mysql:
    logging:
      driver: "json-file"
      options:
        max-size: "100m"
        max-file: "3"

本番環境チェックリスト

セキュリティ

  1. 弱いパスワードは使わない
# 避ける
MYSQL_ROOT_PASSWORD: 123456

# 強いパスワード、または Docker secrets
MYSQL_ROOT_PASSWORD: "Mx8#kL9$pQ2@vN4!"
  1. Docker secrets で機密情報を管理
services:
  mysql:
    image: mysql:8.0
    secrets:
      - mysql_root_password
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password

secrets:
  mysql_root_password:
    file: ./secrets/mysql_root_password.txt
  1. ネットワークを閉じる
networks:
  mysql-network:
    driver: bridge
    internal: true

外部から MySQL を直接晒さず、アプリコンテナ経由にするのが安全です。

運用

  1. 定期バックアップ
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
docker exec mysql-master mysqldump -uroot -prootpwd123 --all-databases > backup_$DATE.sql
tar -czf mysql-data-backup_$DATE.tar.gz /data/mysql/

本番では日次バックアップ、7 日分保持が目安です。

  1. healthcheck
healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p$$MYSQL_ROOT_PASSWORD"]
  interval: 10s
  timeout: 5s
  retries: 3
  start_period: 30s
  1. ログ用 Volume を分離
volumes:
  - ./logs:/var/log/mysql

高可用

  1. 最低 1 主 1 従。読み多ければ 3〜5 スレーブ
  2. MySQL Router、ProxySQL、アプリ層で読み書き分離
  3. 定期的にフェイルオーバー手順を訓練
STOP SLAVE;
RESET SLAVE ALL;
SET GLOBAL read_only=0;

本番の自動切り替えは MHA や Orchestrator など成熟した仕組みを検討してください。

まとめ

本記事の流れを振り返ります。

Docker MySQL の単体デプロイから、データ永続化、設定マウント、外部接続、Docker Compose、本番向け主従レプリケーションまで——よくあるシナリオを一通りカバーしました。

要点:

  1. データ永続化は必須。素のコンテナだけで MySQL を動かさない
  2. 設定ファイルのマウントで文字セットや接続数を先に決める
  3. Docker Composeで設定をコード化し、チームで共有
  4. 主従は手順と設定ファイルさえ合えば難しくない
  5. 本番はセキュリティとバックアップ——弱いパスワード禁止、定期バックアップ

掲載した設定はそのまま試せるものです。パスワードとポートを環境に合わせて変えてください。

初めて Docker で MySQL を触るなら、まず単体で永続化と設定マウントを固めてから主従に進むのがおすすめです。

困ったらまずログ。コメント欄でも質問歓迎です。

Docker で MySQL を回すのは、従来の手動構築よりずっと楽です。一度設定すればどこでも同じ——それが Docker のいちばんの魅力です。

Docker で MySQL をデプロイする完全フロー

データ永続化から主従レプリケーションまで。コンテナ再起動時のデータ消失、設定ファイルマウント、接続失敗などの典型的な問題を解消

⏱️ 目安時間: 1 時間

  1. 1

    ステップ1: 単体デプロイ:データ永続化と設定マウント

    データ永続化:
    • データディレクトリを Volume でマウント:
    docker run -d -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
    • 設定ファイルをマウント:
    docker run -d -v mysql-data:/var/lib/mysql -v ./my.cnf:/etc/mysql/conf.d/my.cnf -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0
    • 環境変数:
    MYSQL_ROOT_PASSWORD、MYSQL_DATABASE、MYSQL_USER、MYSQL_PASSWORD

    永続化の確認:
    • コンテナ削除後に同じ Volume で再作成してもデータが残る
  2. 2

    ステップ2: 主従レプリケーションの設定

    マスター側:
    • binlog を有効化:my.cnf で log-bin=mysql-bin
    • server-id を設定:server-id=1
    • レプリケーション用ユーザーを作成:
    CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
    GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

    スレーブ側:
    • マスター情報を指定:
    CHANGE MASTER TO
    MASTER_HOST='master',
    MASTER_USER='repl',
    MASTER_PASSWORD='password',
    MASTER_LOG_FILE='mysql-bin.000001',
    MASTER_LOG_POS=0;
    • レプリケーション開始:START SLAVE;
    • 状態確認:SHOW SLAVE STATUS\G;
  3. 3

    ステップ3: よくある問題と本番向けデプロイ

    トラブルシュート:
    • 接続失敗:ポートマッピング -p 3306:3306、ネットワーク、ファイアウォールを確認
    • データ消失:Volume 永続化を設定
    • 文字セット:my.cnf で character-set-server=utf8mb4
    • 権限:Volume のパーミッション chmod 755

    本番運用:
    • docker-compose で複数コンテナを管理
    • healthcheck を設定
    • deploy.resources でリソース制限
    • Volume の定期バックアップ
    • 名前付き Volume で運用管理を簡素化

FAQ

Docker で MySQL をデプロイするとき、データ消失を防ぐには?
データ永続化の手順:

1) データディレクトリを Volume でマウント:
docker run -d -v mysql-data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

2) 設定ファイルをマウント:
docker run -d -v mysql-data:/var/lib/mysql -v ./my.cnf:/etc/mysql/conf.d/my.cnf -e MYSQL_ROOT_PASSWORD=123456 mysql:8.0

3) 環境変数:
• MYSQL_ROOT_PASSWORD
• MYSQL_DATABASE
• MYSQL_USER
• MYSQL_PASSWORD

確認:コンテナ削除後に再作成してもデータが残ること。

原因:
コンテナ層にだけデータがあると、コンテナ削除と同時にデータも消える。Volume による永続化が必要。
MySQL の主従レプリケーションはどう設定する?
マスター側:
1) binlog を有効化(my.cnf で log-bin=mysql-bin)
2) server-id を設定(server-id=1)
3) レプリケーション用ユーザーを作成:
CREATE USER 'repl'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';

スレーブ側:
1) マスター情報を指定:
CHANGE MASTER TO
MASTER_HOST='master',
MASTER_USER='repl',
MASTER_PASSWORD='password',
MASTER_LOG_FILE='mysql-bin.000001',
MASTER_LOG_POS=0;
2) START SLAVE で開始
3) SHOW SLAVE STATUS\G で状態確認
Docker MySQL に接続できないときは?
よくある原因:
• 接続失敗:ポートマッピング -p 3306:3306、ネットワーク、ファイアウォール
• データ消失:Volume 永続化未設定
• 文字セット:my.cnf で character-set-server=utf8mb4
• 権限:Volume の chmod 755

確認手順:
1) コンテナ稼働(docker ps)
2) ポートマッピング(docker port container-name)
3) ネットワーク(docker network inspect network-name)
4) ログ(docker logs container-name)

4分で読めます · 公開日: 2025年12月18日 · 更新日: 2026年6月8日

関連記事

コメント

GitHubアカウントでログインしてコメントできます