DockerでMySQLを立てるだけなら、compose定義は十数行で済みます。詰まるのはその先です。2026年7月のリリースからMySQLはカレンダー版番号へ移り、Docker Hubのmysql:latestが指す先は26系のInnovationリリースへ入れ替わりました。ウェブ上に積み上がった「とりあえずlatest」「まずは8.0」という定型は、いま同じ手順をなぞっても同じ結果になりません。
この記事は、タグ選定からcompose定義、初期スキーマの投入、文字コードと認証方式、既存8.0系の引き上げまでを、2026年9月16日時点の公式イメージとentrypointの実装から組み立てました。MySQL本体の概説はMySQLとは?特徴とバージョン選定・採用判断を実装目線で解説に、compose記法そのものはdocker-composeとは?複数コンテナをymlで定義し一括管理する仕組みへ譲ります。PostgreSQL側の同型手順はDocker PostgreSQL構築の手順|18で変わったボリューム位置とcompose定義をどうぞ。
まとめ:着手前に決め切る4点
- タグ:2026年9月16日時点で
latestは26.7.0のInnovation、ltsは9.7.2、旧LTSは8.4.11です。案件で固定するならLTS系をマイナーまで書きます - 8.0系:2026年4月21日からSustaining Supportへ入り、イメージ側も8.0.46を最後に更新が止まっています。新規構築で選ぶ理由はありません
- 初期化:
docker-entrypoint-initdb.dが走るのは、データディレクトリ配下にmysqlディレクトリが無いときだけです。二度目以降は黙って素通りします - healthcheck:初期化中の一時サーバはネットワークを開かずソケットだけで待ち受けます。TCPを明示しないと、まだ使えない状態をhealthyと判定します
公式イメージのタグ一覧でlatestが26系へ切り替わった事実
タグは、後から変えるコストが最も高い決定です。MySQLは版番号の付け方が2026年に変わったため、過去記事の指定を引き写すと意図しない系統を掴みます。
latestとltsと8.4が2026年9月16日時点で指す版番号
Docker Hubのmysql公式リポジトリのタグ一覧を2026年9月16日に実測すると、latest・innovation・26.7.0が同じ更新日で並び、ltsは9.7.2、旧LTS系は8.4.11を指しています。いずれも直近の更新は2026年9月12日です。
この26という数字は26回目のメジャー更新ではありません。MySQL 26.7 Reference Manualの1.3節によれば、InnovationリリースはYY.M.P形式のカレンダー版番号へ移行し、26.7は2026年7月のリリースを指します。同節はLTSについて「5年のpremier supportと3年のextended support」を伴うOracle Lifetime Supportに従うと述べ、Innovationは「次のInnovationリリースまで」しかサポートされないと明記しました。つまりlatestを書くのは、次の四半期に打ち切られる系統へ本番を預ける判断になります。
8.0系がSustaining Supportへ入り更新が止まった実測
8.0系はまだタグとして引けますが、中身は止まっています。同じタグ一覧で8.0は8.0.46を指し、最終更新は2026年5月5日、Debianベースの8.0-bookwormは2026年4月22日で止まっていました。9系と26系にDebianバリアントは並んでおらず、Oracle Linux 9ベースのみです。
MySQLのEOL Noticeは、8.0が2026年4月21日からOracle Sustaining Supportの対象になったと告知し、8.4 LTSまたは9.7 LTSへの移行を案内しています。さらにdocker-library/mysqlのリポジトリを見ると、ビルド定義のディレクトリは8.4・9.7・innovationの3系統だけで、8.0のディレクトリ自体が存在しません。イメージが今後の脆弱性修正を受け取らないことは、この構成から読み取れます。5.7から8.0へ上げた当時の論点はMySQL 5.7と8.0の違いを比較|性能・機能・認証とEOL後の移行先に整理してあります。
本番と開発機とCI環境でイメージタグ固定の粒度を変える判断基準
線は用途で引きます。本番と検証環境は9.7.2のようにマイナーまで固定し、更新をイメージ差し替えという明示的な作業へ落としてください。9.7やltsのような可動タグは、マイナーリリースが出た瞬間に中身が入れ替わります。
開発機とCIは9.7のメジャー固定で足りますが、本番と系統を揃えるのが前提です。開発機だけ26系にして本番が8.4というずれ方をすると、予約語や既定値の差が結合テストの段階まで見えません。手元の実行環境の導入とライセンス境界はDocker Desktopの導入手順とライセンス境界|4.89系の要件と無料代替への移行で確認してください。
docker runで最小構成を起動して接続を確かめるまでの手順
composeへ進む前に、単体コンテナで起動と接続を通すと切り分けが楽になります。ここで通らない構成はcompose化しても通りません。
docker runの最小コマンドと環境変数の指定順を確認する
公式イメージは、rootパスワードに関わる環境変数を何も渡さないと起動を拒否します。MYSQL_ROOT_PASSWORD・MYSQL_ALLOW_EMPTY_PASSWORD・MYSQL_RANDOM_ROOT_PASSWORDのいずれかが要ります。次は9.7 LTSをマイナー固定で起動し、初期DBと接続用ユーザーまで同時に作る形です。
docker run --name mysql-dev \
-e MYSQL_ROOT_PASSWORD=devrootpw \
-e MYSQL_DATABASE=appdb \
-e MYSQL_USER=appuser \
-e MYSQL_PASSWORD=appuserpw \
-p 3306:3306 \
-v mysql-dev-data:/var/lib/mysql \
-d mysql:9.7.2
# 初期化の完了行を確認する(ready for connections が2回出る)
docker logs -f mysql-dev
ログを見る手順を飛ばさないでください。初回起動では初期化が走るため、プロンプトが戻った直後はまだ接続できません。完了を待たずに叩いたアプリ側が接続拒否で落ちる、という詰まり方が最も多い型です。
docker execとmysqlクライアントで接続を確かめる作業
接続確認は、コンテナ内からと、ホストからの2方向で行います。内側が通って外側が通らないなら、原因はポート公開かホスト側の3306番の占有に絞れます。
# コンテナ内から(ソケット経由・ネットワーク層を経由しない)
docker exec -it mysql-dev mysql -uappuser -pappuserpw appdb -e "SELECT VERSION(), @@character_set_server;"
# ホストからTCPで(--protocol=tcp を明示する)
mysql -h 127.0.0.1 -P 3306 --protocol=tcp -uappuser -pappuserpw appdb -e "SELECT 1;"
# ホスト側で3306が既存プロセスに占有されていないか
docker port mysql-dev
localhostを指定するとクライアントがソケット接続へ倒れるため、ホスト側のMySQLへ繋がってしまう事故が起きます。コンテナへ向けたいときは127.0.0.1と--protocol=tcpを組で書いてください。ここまで通れば、compose化は定義を写すだけの作業になります。
compose.yamlにボリュームとhealthcheckを書く手順
案件で使う形はcompose定義です。アプリ側コンテナとの起動順、データの残し方、設定ファイルの当て方をここで決め切ります。
compose.yamlにvolumesと環境変数を書いて起動する
データの置き場所は/var/lib/mysqlです。名前付きボリュームを当てておけば、コンテナを作り直してもデータは残ります。パスワードを定義ファイルへ直書きせず、env_fileで外へ出すところまで最初から入れておきます。
services:
db:
image: mysql:9.7.2
env_file: ./.env.db
ports:
- "3306:3306"
volumes:
- db-data:/var/lib/mysql
- ./docker/mysql/conf.d:/etc/mysql/conf.d:ro
- ./docker/mysql/initdb:/docker-entrypoint-initdb.d:ro
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "--protocol=tcp", "--silent"]
interval: 5s
timeout: 3s
retries: 20
start_period: 30s
app:
build: .
depends_on:
db:
condition: service_healthy
environment:
DATABASE_URL: "mysql://appuser:appuserpw@db:3306/appdb"
volumes:
db-data:
アプリ側からのホスト名はサービス名のdbです。公開ポートの3306はホストの開発ツールから覗くためのもので、コンテナ間の通信には要りません。外へ出さない案件ではportsを落とすか、127.0.0.1:3306:3306と書いて待ち受けを閉じます。
healthcheckでprotocol指定を省くと早すぎるhealthyになる
上の定義で--protocol=tcpを書いた理由がここです。docker-library/mysqlのdocker-entrypoint.shを読むと、初期化フェーズの一時サーバは--daemonize --skip-networking --default-time-zone=SYSTEM --socket=...という引数で起動されます。ネットワークを開かず、Unixドメインソケットだけで待ち受ける状態です。
ここでmysqladmin pingをホスト指定なしで叩くと、クライアントはソケットへ繋ぎに行き、初期化の途中でも応答が返ってきました。healthyと判定されたアプリ側コンテナが起動し、TCPで接続しようとして落ちる、という順序で失敗します。-h 127.0.0.1 --protocol=tcpを付ければ、実際にネットワークが開くまでunhealthyのままになり、待ち時間が意味を持ちます。
depends_onのservice_healthyでアプリ側の起動順を制御する
Docker Composeのservicesリファレンスは、depends_onのlong syntaxでservice_started・service_healthy・service_completed_successfullyの3条件を定義しています。既定の短い書き方はservice_startedに相当し、コンテナが起動した時点で次へ進みます。DBの場合、起動と受付可能は別の状態なので、service_healthyを明示してください。
同リファレンスはhealthcheckの既定値も示しており、intervalとtimeoutは30秒、retriesは3です。初期化に時間がかかるMySQLでは、既定のままだと3回の失敗で打ち切られます。start_periodを30秒ほど取り、その間の失敗を再試行回数に数えない形へ寄せるのが実務的な落とし所になります。
docker-entrypoint-initdb.dで初期スキーマを流し込む方法
開発環境の値打ちは、docker compose upひとつでスキーマとシードまで揃うかどうかで決まります。ただし初期化スクリプトには、一度しか走らないという制約があります。
初期化スクリプトが走る条件とmysqlディレクトリの有無の関係
entrypointはDATABASE_ALREADY_EXISTSという変数を持ち、データディレクトリ配下にmysqlというディレクトリが存在するかどうかで値を決めます。存在すれば初期化済みとみなし、初期化処理ごとdocker-entrypoint-initdb.dを素通りします。
この判定はデータディレクトリが空かどうかではありません。別の中身が残っていても、システムスキーマのディレクトリさえ無ければ初期化が走ります。逆に初期SQLを書き換えてdocker compose upを叩き直しても、ボリュームが残る限り何も起こりません。スキーマを作り直すならdocker compose down -vでボリュームごと落とします。
initdb.dが受け付ける拡張子と実行順の決まり方を押さえる
entrypointが処理する拡張子は.sh・.sql・.sql.bz2・.sql.gz・.sql.xz・.sql.zstで、それ以外は警告を出して無視されます。.shは実行権限があれば実行、無ければsourceされます。実行順はファイル名順なので、番号を前置して依存関係を表現してください。
# docker/mysql/initdb/01_schema.sql
CREATE TABLE orders (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
customer_code VARCHAR(32) NOT NULL,
total_amount DECIMAL(12,2) NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
KEY idx_customer (customer_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
# docker/mysql/initdb/02_seed.sql
INSERT INTO orders (customer_code, total_amount) VALUES
('C-0001', 12800.00),
('C-0002', 4500.00);
投入先のDBはMYSQL_DATABASEで指定したものが既定です。複数DBを作るなら.sh側でCREATE DATABASEから書くか、SQLの先頭でUSEを明示してください。大きなダンプは.sql.gzのまま置けます。
文字コードとタイムゾーンと認証方式をmy.cnfで揃える作業
ここから先は、開発機では表面化せず本番で刺さる設定です。既定値のまま進めて後から直すと、既存データの作り直しが要ります。
my.cnfをマウントして文字コードと照合順序を固定する手順
8.0以降の既定文字セットはutf8mb4ですが、照合順序の既定は版によって差があります。アプリ側の期待と食い違うと、ソート順や一意制約の効き方が環境ごとに変わってしまいました。MySQL公式のDockerデプロイ詳細ページは、設定ファイルのバインドマウントとコマンドラインオプションの両方を指定方法として挙げています。
# docker/mysql/conf.d/my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_0900_ai_ci
default-time-zone = '+09:00'
slow_query_log = 1
long_query_time = 1
slow_query_log_file = /var/log/mysql/slow.log
[client]
default-character-set = utf8mb4
マウント先は/etc/mysql/conf.dで、読み取り専用にしておくとコンテナ側からの書き換えを防げます。/etc/my.cnfを丸ごと差し替える方法もありますが、その場合は同ページが求めるとおり[mysqld]にuser=mysqlを含めないと起動に失敗します。
タイムゾーンをSYSTEMのまま使うと時刻がずれる条件と対処
公式イメージの既定タイムゾーンはUTCです。CURRENT_TIMESTAMPで入れた値と、アプリ側がJSTで表示した値が9時間ずれる形で発覚します。上の定義のようにdefault-time-zoneを明示するか、常にUTCで扱うと決めて統一してください。
名前付きタイムゾーン(Asia/Tokyoのような指定)を使う場合は、タイムゾーンテーブルの投入が要ります。entrypointは初期化時にこれを流し込みますが、MYSQL_INITDB_SKIP_TZINFOを設定すると省略されました。起動を速くしようとこの変数を入れたまま名前付き指定へ切り替えると、設定が不正扱いで弾かれます。
9系でmysql_native_passwordが消えた影響と接続エラー
認証プラグインは、版を上げるときに最も事故が多い箇所です。8.4ではmysql_native_passwordが既定で無効化され、9.0では削除されました。既定はcaching_sha2_passwordです。
実害は、古いドライバやGUIツールから接続できないという形で出ます。PHPの一部構成や旧いJDBC、初期のNode.js向けドライバはcaching_sha2_passwordのハンドシェイクに対応しておらず、認証プラグインを読み込めない旨のエラーで止まりました。8.4までは起動オプションで復活させる逃げ道がありましたが、9系以降はプラグイン自体が無く、クライアント側を対応版へ上げるしかありません。タグを決める前に接続側の対応状況を調べてください。
コンテナを作り直してもデータを残す運用とバックアップ取得の手順
開発環境でも消えると痛いデータは生まれます。置き場所と戻し方を決めていない構成は、事故の当日に判断を迫られます。
名前付きボリュームとバインドマウントを使い分けるときの判断基準
名前付きボリュームはDockerが場所を管理するため権限周りで詰まりにくく、開発機の既定として扱いやすい形です。難点は、ホスト側のどこにあるかが見えにくい点にあります。docker volume inspectで実体のパスは分かるものの、他のバックアップツールから扱うには一手間かかりました。
バインドマウントは逆で、ホストの決まった場所へ実データが置かれるため既存の運用へ載せやすくなります。ただしWindowsやmacOSの共有ファイルシステム上へ置くと、書き込み性能が落ちるうえ権限の扱いも変わります。開発機は名前付きボリューム、バックアップ対象を明示したい検証機はバインドマウント、という切り分けが扱いやすい線です。
mysqldumpで論理バックアップを取り戻せる形で残す手順
取得しているだけで戻せない構成は、バックアップとは呼べません。取得と復元を対で書き、復元まで一度は通してください。
# 取得(コンテナ内のmysqldumpをそのまま使う)
docker compose exec -T db \
mysqldump -uroot -p"$MYSQL_ROOT_PASSWORD" --single-transaction --routines --triggers appdb \
> backups/appdb_$(date +%Y%m%d).sql
# 復元(空のDBへ流し込む)
docker compose exec -T db \
mysql -uroot -p"$MYSQL_ROOT_PASSWORD" appdb < backups/appdb_20260916.sql
InnoDBのテーブルだけなら--single-transactionで停止なしに整合した断面を取れます。復元後は件数と主要な集計値を突き合わせてください。応答が落ちているときの調べ方はスロークエリログとは|MySQLの設定・見方・解析ツールと改善手順にまとめてあります。
受託開発の案件で26系と9.7 LTSと8.4のどれを選ぶかの判断
ここまでの実測を踏まえ、案件でどう決めるかを言い切ります。前提は、開発機とCIと本番を同じ系統で揃えることです。
新規案件で9.7 LTSを既定に置き26系を外すときの採用条件
新規構築の既定は9.7 LTSです。理由は支援期間で、LTSは5年のpremier supportと3年のextended supportを持つ一方、Innovationは次のInnovationが出るまでしか支援されません。四半期ごとに系統ごと乗り換える体制を持てる案件は多くありません。
26系を選ぶ条件は限定的で、26.7で入った機能が要件に直結し、かつ四半期ごとの追随を運用計画へ書き込める場合に限ります。検証目的で触るのは構いませんが、その環境を本番へ流用しないでください。8.4 LTSは、接続するドライバが9系の認証方式へ追いついていない案件の逃げ道です。その場合も9.7へ上げる期限を決めてから着手します。
8.0のまま動いている既存案件を引き上げる順番と移行時の判断
8.0で動いている既存環境は、止まっているイメージの上に乗っています。順番としては、まず接続側のドライバをcaching_sha2_password対応版へ上げ、次に検証環境で8.4へ、そこから9.7へ進めます。LTSは系統を飛ばせない決まりがあるため、8.4を経由せずに9.7へ跳ぶ形は選べません。
移行の判断材料は認証方式と予約語、それに既定値の差です。特にmysql_native_passwordで作られた既存ユーザーは、プラグインが消えた版では認証できません。移行前にALTER USERで切り替え、8.4の段階で全ユーザーの認証プラグインを揃えてから9.7へ進むと、切り分けが一段簡単になります。
開発機のDockerとマネージドDBのどちらへ寄せるかの線引き
Docker上のMySQLは、開発環境とCIでこそ力を発揮します。本番まで自前のコンテナで持つかは別の判断で、バックアップの世代管理、フェイルオーバー、パッチ適用の当番を誰が持つかで決まります。当番を置けないなら、本番はマネージドサービスへ寄せ、開発機だけコンテナで揃えるのが現実的です。
その線引きを含めた構成の組み立ては、インフラ構築(AWS・Google Cloud・Azure)で現行構成の棚卸しから相談できます。版計画と移行手順まで含めた設計も同じ窓口です。
よくある質問
DockerでMySQLを構築するときに検索されている質問へ、公式イメージの実装に基づいて答えます。
docker composeでMySQLのバージョンを上げるにはどうすればよいですか?
マイナー更新ならタグを書き換えて再起動すれば済みます。メジャーをまたぐ場合は、先にmysqldumpで論理バックアップを取り、認証プラグインとドライバの対応を確認してから進めてください。LTSは系統を飛ばせない決まりがあり、8.4から26系へ直接という上げ方は選べません。
コンテナを作り直すとデータが消えてしまうのはなぜですか?
/var/lib/mysqlへボリュームが当たっていない可能性が高いと考えられます。マウント指定が無いと、データは匿名ボリュームへ書かれ、コンテナを削除すると追跡できなくなります。compose定義のvolumesにサービス側とトップレベル側の両方の記述があるか確認してください。docker compose down -vは名前付きボリュームごと消す操作です。
初期化SQLが実行されないときはどこを見ればよいですか?
まずデータディレクトリ配下にmysqlディレクトリが残っていないかを確認します。entrypointはその有無で初期化済みかを判定するため、既存ボリュームが残っていれば初期SQLは走りません。次に拡張子を見てください。処理対象は.sh・.sqlと圧縮形式のみです。マウント先の綴りと読み取り権限も確かめます。
ホストのGUIツールからコンテナのMySQLへ接続できないのはなぜですか?
層を分けて切り分けます。コンテナが起動しているか、ポートがホスト側へ割り当てられているか、既存のMySQLが3306を占有していないかの順です。接続先にlocalhostを書いているならソケット接続へ倒れている可能性があるため、127.0.0.1へ変えてください。9系では認証プラグインの削除があり、ツール側が古いと認証で弾かれます。
mysql:latestを本番で指定してはいけないのはなぜですか?
2026年9月16日時点のlatestはInnovationリリースの26.7.0を指します。Innovationは次のInnovationが出るまでしか支援されず、非推奨機能の削除や挙動変更も想定された系統です。しかも可動タグなので、イメージを引き直した日によって別の版が降ってきます。本番と検証は9.7.2のようにマイナーまで固定してください。
関連記事
- MySQLとは?特徴とバージョン選定・採用判断を実装目線で解説:MySQL本体の概説と採用判断
- docker-composeとは?複数コンテナをymlで定義し一括管理する仕組み:compose記法そのものの基礎
- Docker PostgreSQL構築の手順|18で変わったボリューム位置とcompose定義:PostgreSQL側の同型手順
- MySQL 5.7と8.0の違いを比較|性能・機能・認証とEOL後の移行先:旧版からの移行判断
- Docker Desktopの導入手順とライセンス境界|4.89系の要件と無料代替:手元の実行環境の整え方