2026年8月3日
2026年8月3日
DockerでMySQLを動かす方法|データの永続化・初期設定・接続・バックアップまで
DockerでMySQLを動かすときは、公式の mysql イメージに環境変数でrootパスワードを渡し、データディレクトリ /var/lib/mysql をボリュームに保存する、という形が基本です。ここまでは数行で済みますが、「環境変数を変えたのに反映されない」「コンテナを消したらデータも消えた」「古いクライアントから接続できない」といったつまずきが多いのもMySQLのコンテナです。この記事では、起動から永続化、初期データの投入、設定の変更、接続、バックアップと更新までを、つまずく理由とあわせて説明します。
前提はLinux上のDocker EngineとDocker Compose v2です。MySQLのバージョンは長期サポート版の8.4系を例にしています(2026年10月現在)。8.0系はOracleがサポート終了の時期を2026年4月と案内していたため、新しく始めるなら8.4系を選ぶのが無難です。最新のサポート状況はMySQLの公式サイトで確認してください。
最小構成で起動する
docker volume create mysql-data
docker run -d --name mysql \
-e MYSQL_ROOT_PASSWORD=change-this-root-password \
-e MYSQL_DATABASE=appdb \
-e MYSQL_USER=appuser \
-e MYSQL_PASSWORD=change-this-app-password \
-v mysql-data:/var/lib/mysql \
-p 127.0.0.1:3306:3306 \
mysql:8.4
それぞれの指定には理由があります。
MYSQL_ROOT_PASSWORD:rootのパスワード。これ(または空パスワードやランダム生成を許可する変数)がないとコンテナは起動直後に終了します。MYSQL_DATABASE/MYSQL_USER/MYSQL_PASSWORD:アプリ用のデータベースとユーザーを作り、そのデータベースへの権限を与えます。アプリからはrootではなくこのユーザーで接続するのが基本です。-v mysql-data:/var/lib/mysql:データを名前付きボリュームに保存します。これがないと、docker rmでコンテナを消した時点でデータも消えます。-p 127.0.0.1:3306:3306:ホスト自身からだけ接続できるようにします。127.0.0.1:を付けずに公開すると、Dockerが追加するiptablesのルールによってufwなどの設定を素通りし、インターネットからDBのポートが見えてしまうことがあります。
起動したら、初期化が終わるまで少し待ちます。
docker logs -f mysql
初回はデータディレクトリの作成とユーザーの作成が走り、そのあと本番用のサーバーが起動し直します。ログに ready for connections が出て、初期化の終了を示すメッセージのあとにもう一度 ready for connections が出れば準備完了です。表示される文言はバージョンで多少変わります。
環境変数は「初回だけ」効く
いちばん多い誤解がこれです。MYSQL_ROOT_PASSWORD や MYSQL_DATABASE などは、データディレクトリが空のときの初期化処理でだけ使われます。すでにデータの入ったボリュームを使って起動した場合、環境変数を変えても何も変わりません。
「パスワードを変えたのにログインできない」「データベースが作られない」という場合は、古いボリュームが残っていないかを確認します。
docker volume ls
docker inspect mysql --format '{{ json .Mounts }}'
開発中で中身が不要なら、ボリュームを削除して作り直せば初期化からやり直せます。本番データが入っている場合は、ボリュームを消さずにSQLでパスワードを変更してください。
ALTER USER 'appuser'@'%' IDENTIFIED BY 'new-password';
初期データを投入する
/docker-entrypoint-initdb.d に置いた .sql、.sql.gz、.sh のファイルは、初期化のときに名前順で実行されます。テーブル定義やテスト用のデータを入れておく用途に便利です。
docker run -d --name mysql \
-e MYSQL_ROOT_PASSWORD=change-this-root-password \
-e MYSQL_DATABASE=appdb \
-v mysql-data:/var/lib/mysql \
-v "$(pwd)/initdb:/docker-entrypoint-initdb.d:ro" \
mysql:8.4
これも環境変数と同じく、データディレクトリが空のときだけ実行されます。MYSQL_DATABASE を指定していれば、SQLファイルはそのデータベースを対象に実行されます。サイズの大きいダンプを置くと初期化に時間がかかり、その間はアプリから接続できない点に注意してください。
設定を変える
MySQLの設定は、/etc/mysql/conf.d/ に .cnf ファイルを置いて追加します。
# conf.d/my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_0900_ai_ci
max_connections = 200
slow_query_log = ON
long_query_time = 1
docker run -d --name mysql \
-e MYSQL_ROOT_PASSWORD=change-this-root-password \
-v mysql-data:/var/lib/mysql \
-v "$(pwd)/conf.d:/etc/mysql/conf.d:ro" \
mysql:8.4
設定ファイルを使わず、イメージ名のうしろに --max-connections=200 のようにオプションを並べる方法もあります。MySQL 8.0以降は既定の文字コードがutf8mb4なので、日本語や絵文字を扱うためだけに文字コードの設定を足す必要は通常ありません。反映されたかは SHOW VARIABLES LIKE 'max_connections'; で確認します。
なお、ホスト側で作った .cnf が誰でも書き込める権限(777など)になっていると、MySQLは安全のためにそのファイルを無視します。設定が効かないときはファイルの権限も確認してください。
接続する
コンテナの中のクライアントを使うのが手軽です。
docker exec -it mysql mysql -u appuser -p appdb
ホストのアプリやGUIツールからは 127.0.0.1:3306 に接続します。localhost と書くとクライアントによってはUNIXソケットを探しに行き、Can't connect to local MySQL server through socket で失敗するので、127.0.0.1 と書くほうが確実性が高くなります。
同じComposeファイル内の別のコンテナからは、サービス名(例:db)がホスト名になります。この場合はコンテナ同士の内部ネットワークで届くので、ホストにポートを公開する必要はありません。
古いクライアントで認証エラーが出る場合
MySQL 8.0以降の既定の認証方式は caching_sha2_password です。古いライブラリやツールで接続すると、認証プラグインを読み込めないというエラーが出ることがあります。8.4系では旧方式の mysql_native_password は既定で無効になっており、9.x系では削除されています。まずはクライアントやドライバを新しい版に更新するのが正攻法です。旧方式を有効にする起動オプションもありますが、廃止に向かう方式なので一時的な回避にとどめてください。
Composeで書く場合とヘルスチェック
アプリと一緒に動かすなら、Composeでまとめると管理しやすくなります。パスワードは .env ファイルに分け、Gitには含めないようにします。
services:
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: appdb
MYSQL_USER: appuser
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--silent"]
interval: 10s
timeout: 5s
retries: 10
start_period: 30s
restart: unless-stopped
volumes:
mysql-data:
ヘルスチェックで -h 127.0.0.1 とTCPを指定しているのには理由があります。初期化中に一時的に動くサーバーはネットワーク接続を受け付けない設定で起動するため、ソケット経由で確認すると初期化が終わる前に「正常」と判定されてしまうことがあるからです。mysqladmin ping はサーバーが応答すれば認証に失敗しても成功を返す仕様なので、ここにパスワードを書く必要はありません。アプリ側は depends_on の condition: service_healthy で、DBの準備ができてから起動するようにできます。
環境変数で渡したパスワードは docker inspect で見えます。共有サーバーなどでこれを避けたい場合は、MYSQL_ROOT_PASSWORD_FILE のように _FILE を付けた変数でファイルのパスを渡し、Composeのsecretsと組み合わせる方法があります。
バックアップと復元
ボリュームのファイルを稼働中にコピーするのは、整合性が取れない状態になりやすいので避けます。論理バックアップは mysqldump で取るのが手堅い方法です。
# バックアップ(InnoDBなら --single-transaction でロックを最小限に)
docker exec mysql sh -c 'exec mysqldump --single-transaction --all-databases -uroot -p"$MYSQL_ROOT_PASSWORD"' > all-databases.sql
# 復元
docker exec -i mysql sh -c 'exec mysql -uroot -p"$MYSQL_ROOT_PASSWORD"' < all-databases.sql
パスワードをシングルクォートの中で $MYSQL_ROOT_PASSWORD と書いているのは、ホストのシェルではなくコンテナ内の環境変数を展開させるためです。取ったファイルは別のサーバーやストレージにも保存し、ときどき復元のテストまで行っておくと、いざというときに慌てずに済みます。
バージョンを上げるとき
- 作業前に
mysqldumpでバックアップを取ります。ボリュームのスナップショットが取れる環境なら併用します。 - メジャーバージョンを飛ばして上げない(8.0から8.4へ、のように順に上げる)のが基本です。上げたあとのダウングレードは基本的にできないと考えてください。
- 開発環境やステージングで、同じデータを使って先に試します。
mysql:8.4のように系列でタグを固定し、latestは使わないようにします。latestのままpullすると、意図せずメジャーバージョンが上がることがあります。
まとめ
MYSQL_ROOT_PASSWORDを指定し、/var/lib/mysqlを名前付きボリュームに保存するのが基本形です。- 環境変数と
/docker-entrypoint-initdb.dは、データディレクトリが空の初回だけ効きます。 - 設定は
/etc/mysql/conf.d/の.cnfで追加し、反映をSHOW VARIABLESで確認します。 - ポートは
127.0.0.1:を付けて公開し、ホストからは127.0.0.1で接続します。古いクライアントの認証エラーはクライアント側の更新で解決するのが基本です。 - バックアップは
mysqldump --single-transactionで取り、更新前にはバックアップとステージングでの確認を行います。