Docker Composeで作るPHP・MySQLのローカル開発環境


Dockerで作った開発環境が「本当に立ち上がったか」は、docker compose ps の STATUS が全行 Up になっていて、PORTS の -> の左側に 0.0.0.0: が付いているかで判定できます。ブラウザで真っ白なページが出たときに、コンテナが落ちているのか、ポートがホストに出ていないのか、アプリ側のエラーなのかを切り分けられるのはこの1行です。

MAMPやXAMPPでローカル環境を作った次に出てくる悩みは、たいてい「自分のMacでは動くのに、他のメンバーの環境では動かない」「本番のPHPバージョンと手元が揃っていない」の2つです。Docker Composeは、この2つを設定ファイル1枚に押し込んで、チーム全員が同じ手順で同じ環境を起動できるようにするための道具です。

ところが、ネット上に転がっているDocker Composeのサンプルは、そのまま貼っても動かないものが少なくありません。Apple SiliconのMacに対応していないイメージを指定していたり、いまは警告が出るだけの古い記述が残っていたりするからです。この記事の構成は、実際にApple Silicon(Darwin arm64)のMacで起動して確認したものだけを載せています。

この記事では、PHP+MySQL+phpMyAdminの開発環境を compose.yaml で立ち上げ、それが動いていることを自分で判定し、動かなかったときにエラーメッセージから原因を1手で特定するところまでを扱います。Dockerそのものの網羅的な入門ではなく、「MAMP/XAMPPの次」に絞った内容です。


MAMP・XAMPPの次にDockerを選ぶ理由

Dockerとは、アプリケーションと、その動作に必要なライブラリ・設定・ミドルウェアをひとまとめにして、どのマシンでも同じ状態で起動できるようにするコンテナ型の実行環境です。MAMPやXAMPPが「自分のPCにPHPとMySQLを入れる」道具であるのに対し、Dockerは「PHPとMySQLが入った箱を、設定ファイルどおりに毎回作り直す」道具だと考えると差がつかみやすくなります。

「自分の環境では動く」が通用しなくなる地点

ひとりで作っている間は、MAMPで十分です。問題が出るのは、次のどれかに当たったときです。

  • 複数人で同じリポジトリを触るようになり、メンバーのPHPバージョンが揃わなくなった
  • 案件ごとにPHPやMySQLのバージョンが違い、1つのMAMPで両立できなくなった
  • 本番サーバーの構成に手元を寄せたいが、GUIの設定画面では細かく合わせられない
  • 新しいメンバーの環境構築に半日かかり、手順書がすぐ陳腐化する

Docker Composeを使うと、この4つはすべて「compose.yaml をリポジトリに置いて、全員が docker compose up -d を叩く」に置き換わります。手順書が設定ファイルそのものになるので、陳腐化しません。

MAMPを消す必要はない

DockerとMAMPは排他ではありません。既存案件はMAMPのまま、新しい案件からDockerにする、という併存が普通です。ぶつかるのはホスト側のポートだけなので、MAMPを起動したままDockerを使う場合は、あとで説明する ports: の左側の番号を重ならないようにしておけば問題ありません。

MAMP・XAMPPそのものの導入手順や、どちらを選ぶかの判断は別記事にまとめています。

MAMPとXAMPPの導入手順と選び方はこちら


Docker Composeとcompose.yamlの基本

Docker Composeとは、複数のコンテナ(Webサーバー、データベース、管理ツールなど)の構成を1つのYAMLファイルに書き、まとめて起動・停止するためのツールです。Docker Desktopを入れると同梱されるので、個別のインストールは不要です。

ファイル名は compose.yaml が正

古い記事では docker-compose.yml という名前が使われていますが、現在の公式ドキュメントは compose.yaml を推奨しています。Docker公式のCompose application modelには「The default path for a Compose file is compose.yaml (preferred) or compose.yml」「If both files exist, Compose prefers the canonical compose.yaml」と明記されています。docker-compose.yml も後方互換として読まれるため、既存プロジェクトのファイル名を急いで変える必要はありません。新規に作るなら compose.yaml です。

コマンドは docker compose(スペース区切り)

ハイフンでつないだ docker-compose はPythonで書かれたCompose v1のコマンドです。Docker公式ブログ「Docker Compose: What’s New, What’s Changing, What’s Next」で「Compose V1 support will no longer be provided after June 2023 and will be removed from all future Docker Desktop versions」と告知されており、現行はGoで書かれたv2以降です。Docker公式のHistory and development of Docker Composeによると、2026年8月時点でサポートされているCLIはCompose v2とCompose v5の2つで、どちらも docker compose というスペース区切りの形で呼び出します。

混乱しやすいのは、いまのDocker Desktopでも docker-compose と打つと動いてしまう点です。これはv1が生き残っているのではなく、Docker Desktopが同名のシンボリックリンクを用意しているだけです。手元のmacOS(Docker Desktop同梱のCompose v2.32.4)で確認すると、/usr/local/bin/docker-compose/Applications/Docker.app/Contents/Resources/cli-plugins/docker-compose へのリンクで、docker-compose version の出力も Docker Compose version v2.32.4-desktop.1 でした。つまり動いているのは最初からv2です。新しく覚えるならスペース区切りだけで十分です。

最低限おぼえる6コマンド

コマンドやること使うタイミング
docker compose up -d全サービスをバックグラウンドで起動作業開始時
docker compose ps起動状態とポートの割り当てを表示動いているか確認するとき
docker compose logs db指定サービスのログを表示起動に失敗したとき
docker compose exec db bash起動中のコンテナ内でコマンド実行DBに入って調べるとき
docker compose downコンテナとネットワークを削除(名前付きvolumeは残る)作業終了時
docker compose down -v名前付きvolumeまで削除DBを初期状態に戻したいとき

downdown -v の違いは、後述するデータ永続化の話に直結します。-v を付けるとDBの中身が消えるので、普段の終了時は付けないでください。


PHP+MySQL+phpMyAdminの開発環境をcompose.yamlで作る

ここからが本体です。次の2ファイルを作るだけで、PHPが動くWebサーバー・MySQL・phpMyAdminの3つが立ち上がります。まず作業用のディレクトリを1つ作ってください。ここでは docker-php という名前で進めます。

compose.yaml を置く

services:
  web:
    image: php:8.4-apache
    ports:
      - "8080:80"
    volumes:
      - ./src:/var/www/html

  db:
    image: mysql:8.4
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: sample_db
    volumes:
      - db_data:/var/lib/mysql

  phpmyadmin:
    image: phpmyadmin
    ports:
      - "8081:80"
    environment:
      PMA_HOST: db

volumes:
  db_data:

読み方のポイントは3つです。ports:"8080:80" は「ホストの8080番をコンテナの80番につなぐ」という意味で、左がMacやWindows側、右がコンテナ側です。volumes:./src:/var/www/html は手元の src ディレクトリをドキュメントルートに割り当てる指定で、ファイルを保存すればそのままブラウザに反映されます。db_data:/var/lib/mysql はDBの保存先をコンテナの外(名前付きvolume)に逃がす指定で、これが無いとコンテナを作り直した瞬間にテーブルが消えます。

動作確認用のPHPファイルを置く

src/index.php を作り、次の1行だけ書きます。バージョンを出力させておくと、あとで「本当にコンテナのPHPが動いているか」を目視で確認できます。

<?php
echo "PHP " . PHP_VERSION . " is running in a container.\n";

起動する

cd docker-php
docker compose up -d

初回はイメージのダウンロードが入るため数分かかります。2回目以降はキャッシュが効くので数秒です。

古いサンプルをそのまま貼ると動かない理由

上のファイルには version: '3' の行がなく、イメージのタグも他所でよく見るものとは違います。理由は次のとおりです。

よく見る記述2026年8月時点の状況この記事での書き方
version: ‘3’Compose Specificationでobsolete(情報としてしか扱われず、使うと警告が出る)行ごと書かない
image: mysql:5.7arm64版のイメージが存在せず、Apple Silicon Macでは起動に失敗する。MySQL 5.7自体もSustaining Support入りmysql:8.4(LTS・arm64対応)
image: php:8.1-apachePHP 8.1はセキュリティ修正も終了済みphp:8.4-apache
db に volumes を書かないコンテナを作り直すとDBの中身が消える名前付きvolume db_data を割り当てる

version については、Docker公式のVersion and name top-level elementsに「The top-level version property is defined by the Compose Specification for backward compatibility. It is only informative and you’ll receive a warning message that it is obsolete if used」と書かれています。実際にこの行を残したまま docker compose up -d を実行すると、毎回次の警告が出ます。

level=warning msg="docker-compose.yml: the attribute `version` is obsolete, it will be ignored, please remove it to avoid potential confusion"

MySQLのタグは、Apple Siliconユーザーにとってはもっと切実です。Docker Hubのレジストリで対応プラットフォームを確認すると、mysql:5.7linux/amd64 しか持っていないのに対し、mysql:8.4linux/amd64linux/arm64/v8 の両方を持っています。そのため mysql:5.7 を指定したまま起動すると、Apple Silicon Macでは次のエラーでcompose全体が止まります。

db Error no matching manifest for linux/arm64/v8 in the manifest list entries: no match for platform in manifest: not found
Error response from daemon: no matching manifest for linux/arm64/v8 in the manifest list entries: no match for platform in manifest: not found

各タグがどのプラットフォームに対応しているかはDocker HubのMySQL公式イメージのページで確認できます。バージョンのサポート状況はOracleのMySQL End-of-Life Announcementsが一次情報で、MySQL 5.7は2023年10月25日から、MySQL 8.0も2026年4月21日からSustaining Supportに移っており、8.4 LTSへの移行が案内されています。PHP側はphp.netのSupported Versionsが一次情報で、8.1はすでにセキュリティ修正も終了、2026年8月時点でアクティブサポート中なのは8.4と8.5です。


環境が本当に動いているかを自分で判定する

docker compose up -d がエラーなく終わっても、それは「コンテナの作成に成功した」だけで、中のプロセスが生きているかは別の話です。次の4つを順に実行すると、どこまで通っていてどこで止まっているかが確定します。

docker compose ps
curl -o /dev/null -w "%{http_code}\n" http://localhost:8080/
curl -o /dev/null -w "%{http_code}\n" http://localhost:8081/
docker compose exec -T db mysql -uroot -prootpass -e "SHOW DATABASES;"

exec に付けた -T は疑似ターミナルを割り当てないオプションです。手打ちなら無くても動きますが、シェルスクリプトやCIから叩くときは付けないと落ちることがあるので、最初から付けて覚えておくと安全です。

合否ライン

確認したいこと合格不合格
コンテナが起動しているdocker compose ps のSTATUSが全行 UpExited と出る/行が1つも出ない
ポートがホストに出ているPORTSが 0.0.0.0:8080->80/tcp(->の左に 0.0.0.0: が付く)80/tcp としか出ない
ブラウザから届くHTTPステータス 200000(接続できない)/404(ファイルが無い)
phpMyAdminが応答する8081でもHTTPステータス 200000/502
DBが応答するSHOW DATABASES; の一覧に sample_db が並ぶCan’t connect / Access denied
データが消えないdown のあと up し直してもレコードが残るTable … doesn’t exist

実際にApple SiliconのMacで上の構成を起動したときの docker compose ps の出力は次のとおりでした。

NAME                      IMAGE            COMMAND                  SERVICE      CREATED          STATUS          PORTS
docker-php-db-1           mysql:8.4        "docker-entrypoint.s…"   db           19 seconds ago   Up 15 seconds   3306/tcp, 33060/tcp
docker-php-phpmyadmin-1   phpmyadmin       "/docker-entrypoint.…"   phpmyadmin   19 seconds ago   Up 15 seconds   0.0.0.0:8081->80/tcp
docker-php-web-1          php:8.4-apache   "docker-php-entrypoi…"   web          19 seconds ago   Up 15 seconds   0.0.0.0:8080->80/tcp

NAMEの先頭に付く docker-php はプロジェクト名で、compose.yamlを置いたディレクトリ名から自動で決まります。別の名前のディレクトリで作業していれば、その名前になります。

dbのPORTSに 0.0.0.0: が付かないのは正常

この記事でいちばん誤解されやすいのがここです。上の出力で db の行だけ 3306/tcp, 33060/tcp となっていて、0.0.0.0: が付いていません。これは壊れているのではなく、compose.yamldbports: を書いていないからです。

Composeは同じプロジェクトのコンテナを1つのネットワークに入れ、サービス名でお互いを引けるようにします。web のコンテナから確認すると db という名前がコンテナのIPアドレスに解決され、3306番も開いていました。一方、Mac側から localhost:3306 に接続しようとすると失敗します。つまり「ホストには出ていないが、コンテナ同士では到達できる」状態が正常です。phpMyAdminの PMA_HOST: db が動くのも、この名前解決のおかげです。

逆に、TablePlusやSequel Aceのようなクライアントアプリから直接つなぎたい場合だけ、db にも ports: を足してホストに出します。必要がないなら出さないほうが安全です。

データが消えないことまで確認する

「立ち上がった」で満足すると、翌日に泣きます。down したあとに up し直してデータが残るかまで確認して、はじめて開発環境として使えます。

docker compose exec -T db mysql -uroot -prootpass sample_db \
  -e "CREATE TABLE memo (id INT AUTO_INCREMENT PRIMARY KEY, body VARCHAR(50)); INSERT INTO memo (body) VALUES ('persisted');"

docker compose down
docker compose up -d

docker compose exec -T db mysql -uroot -prootpass sample_db -e "SELECT * FROM memo;"

合格なら最後の SELECT でレコードが返ります。実測では次のとおりでした。

mysql: [Warning] Using a password on the command line interface can be insecure.
id      body
1       persisted

先頭のWarningは「コマンドラインにパスワードを直書きするのは安全ではない」という注意で、ローカル開発では無視して構いません。db から volumes: を外した構成で同じ手順を試すと、up し直したあとの SELECT は次のように失敗します。この差が、名前付きvolumeを書く理由です。

ERROR 1146 (42S02) at line 1: Table 'sample_db.memo' doesn't exist

起動しないときにエラーメッセージから原因を1手で決める

Dockerのエラーは、文面と原因がほぼ1対1で対応しています。全文を読まなくても、次の表の左列と照合すれば打つべき手が決まります。

出たメッセージ・症状意味打つ手(1つだけ)
Cannot connect to the Docker daemon … Is the docker daemon running?Docker Desktopが起動していないDocker Desktopを起動し、クジラのアイコンが動き終わってから再実行
no matching manifest for linux/arm64/v8 …そのタグにApple Silicon用のイメージが無いarm64対応のタグへ変更する(mysql:5.7 なら mysql:8.4)
WARNING: The requested image’s platform (linux/amd64) does not match the detected host platform …警告であってエラーではない。エミュレーションで起動するそのまま使ってよい。動作が遅ければarm64対応タグへ変更
the attribute `version` is obsolete …Compose v1時代の version: 行が残っているversion: の行を削除する
Bind for 0.0.0.0:8080 failed: port is already allocatedホストの8080番が別のコンテナやアプリに使われているports: の左側の番号だけ変える(”8090:80″ など)
PORTSに 0.0.0.0: が付かないそのサービスに ports: を書いていないホストに出したいサービスにだけ ports: を追加する
down → up でデータが消える(Table … doesn’t exist)名前付きvolumeを割り当てていないdb に db_data:/var/lib/mysql を追加する

ポート競合のメッセージは、ふさいでいるのが他のコンテナかホスト側のアプリかで文面が変わります。手元で別コンテナが8080を掴んでいる状態を作ったときは Bind for 0.0.0.0:8080 failed: port is already allocated でした。MAMPなどホスト側のアプリが掴んでいる場合は文言が異なることがありますが、いずれも対処は同じで、ports: の左側の番号を空いている番号に変えるだけです。

表に無いエラーはログを見る

docker compose ps で該当サービスが Exited になっている場合、原因はコンテナの中にあります。サービス名を指定してログを出せば、たいてい最終行に理由が書かれています。

docker compose logs db --tail 30

MySQLでよくあるのは、以前に別のパスワードで初期化した名前付きvolumeが残っていて、MYSQL_ROOT_PASSWORD を変えたのに反映されないケースです。初期化は最初の起動時にしか走らないためで、開発環境なら docker compose down -v でvolumeごと消してから起動し直すのがいちばん速い解決です。


Dockerと仮想マシンは何が違うのか

コンテナと仮想マシンは、どちらも実行環境を分離する技術ですが、分離するレイヤーが違います。仮想マシンはゲストOSごと丸ごと立ち上げるのに対し、コンテナはOSのカーネルを共有してプロセスを隔離します。これが起動時間とリソース消費の差になります。

比較項目Docker(コンテナ)仮想マシン(VM)
起動速度秒単位分単位
ディスク消費イメージ単位で共有され小さいゲストOSぶんが丸ごと必要
OSの扱いカーネルを共有し、プロセスとして隔離ゲストOSが個別に起動
分離レベルプロセスレベルハードウェアレベル
持ち運びイメージとcompose.yamlだけで再現VMイメージが大きく移動コストが高い
代表的なツールDocker, PodmanVirtualBox, VMware, Hyper-V

Mac・Windowsでは「ホストOSのカーネルを共有」しない

入門記事でよく見る「ホストOSのカーネルを共有するから軽い」という説明は、Linuxをホストにした場合の話です。MacやWindowsでDocker Desktopを使っている場合、コンテナはmacOSやWindowsのカーネルではなく、Docker Desktopが用意するLinux仮想マシンのカーネルを共有します。Docker公式のVirtual Machine Managerのドキュメントにも「Docker Desktop supports multiple Virtual Machine Managers (VMMs) to power the Linux VM that runs containers」と書かれています。

手元のMacで確認すると、ホスト側の uname -smDarwin arm64 を返す一方、docker info が返すサーバー側は linux/aarch64、カーネルは 6.12.5-linuxkit でした。Darwinのカーネルは影も形もありません。この事実を知っておくと、「Macなのにコンテナ内はLinuxのパスなのはなぜか」「ファイル同期がときどき遅いのはなぜか」といった疑問が一気につながります。

Linux仮想マシンを1つ常駐させる以上、メモリは相応に必要です。ローカル開発機を選ぶ段階から見直したい場合は、こちらも参考にしてください。

Web制作者向けノートPCの選び方はこちら


実務で最初に詰まる2つのこと

手元で動いたあとに待っているのは、技術ではなく制限のほうです。とくに次の2つは、チームで使い始めた段階で必ず一度は当たります。

Docker Hubのpull回数制限

イメージの取得(pull)には回数制限があります。Docker公式のDocker Hub usage and rate limitsによると、未ログインの場合はIPv4アドレスまたはIPv6の/64サブネット単位で6時間あたり100回、無料の個人アカウントでログインすると6時間あたり200回、Pro以上の有料プランは無制限です。上限に達するとHTTP 429が返ります。

ここで効いてくるのが「IP単位」という点です。オフィスやコワーキングスペースのように出口IPを共有している環境では、自分は数回しかpullしていなくても上限に当たることがあります。CIから何度もpullする構成でも同じです。対策は単純で、docker login して自分のアカウントに紐づけておくことです。

Docker Desktopのライセンス

Docker Desktopは無条件の無料ソフトではありません。Docker公式のDocker Desktop license agreementでは、個人利用・教育目的・非商用のオープンソースプロジェクト、および「fewer than 250 employees AND less than $10 million in annual revenue」の小規模事業者は無料と定められています。従業員250名以上、または年間売上1000万ドル以上の組織で業務利用する場合は、有料サブスクリプション(Pro / Team / Business)が必要です。

フリーランスや小規模の制作会社であれば無料枠に収まりますが、常駐先の企業規模によっては条件が変わります。クライアント環境に導入を提案する前に、規模の条件を確認しておくと後から揉めません。

ローカルで作った構成をそのまま本番に持っていくわけにはいきません。サーバー側の話は別記事で扱っています。

AWSにWordPressを構築する手順はこちら

クライアント案件のサーバー構築の進め方はこちら


よくある質問(FAQ)

Q. MAMPを消さないとDockerは使えませんか?

消す必要はありません。DockerとMAMPは別々の仕組みなので併存できます。ぶつかるのはホスト側のポート番号だけなので、compose.yamlの ports: の左側(ホスト側)をMAMPが使っている番号と重ならないように設定すれば、両方を起動したままでも動きます。

Q. docker-compose と docker compose はどちらを使えばよいですか?

スペース区切りの docker compose を使ってください。ハイフン付きの docker-compose はCompose v1のコマンドで、Docker公式のアナウンスどおり2023年6月以降サポートが終了し、以降のDocker Desktopには同梱されていません。現在のDocker Desktopで docker-compose が動くのは、同名のシンボリックリンクがv2のバイナリを指しているためで、v1が動いているわけではありません。

Q. Apple SiliconのMacで他サイトのサンプルが動かないのはなぜですか?

指定されているイメージにarm64版が存在しないためです。代表例が mysql:5.7 で、Docker Hubのレジストリ上は linux/amd64 しか持たないため、Apple Silicon Macでは no matching manifest for linux/arm64/v8 というエラーでcompose全体が起動しません。arm64にも対応している mysql:8.4 などに変更するか、どうしても5.7が必要なら該当サービスに platform: linux/amd64 を書いてエミュレーションで動かします。

Q. コンテナを削除するとデータは消えますか?

コンテナの中に置いたデータは消えます。ただし volumes: で名前付きvolumeを割り当てておけば、データはコンテナの外に保存されるため、docker compose down でコンテナを削除しても残ります。消えるのは docker compose down -v のように -v を付けたときで、この記事のcompose.yamlは db_data を割り当てているので、通常の down ではDBの中身は保持されます。

Q. Docker Desktopは無料で使えますか?

個人利用・教育目的・非商用のオープンソースプロジェクト、および従業員250名未満かつ年間売上1000万ドル未満の小規模事業者は無料です。それ以外の組織で業務利用する場合は、Pro / Team / Businessのいずれかの有料サブスクリプションが必要になります。条件はDocker公式のライセンスページに明記されているので、導入前に自社・常駐先の規模を確認してください。

Q. DockerでWordPressの開発環境も作れますか?

作れます。この記事の web サービスをWordPress公式イメージに差し替え、データベース接続用の環境変数を渡す形になります。確認の手順はこの記事とまったく同じで、docker compose ps でSTATUSとPORTSを見て、8080番にHTTPステータス200が返るかを確かめれば判定できます。

Q. DockerとKubernetesの違いは何ですか?

Dockerは1台のマシンでコンテナを作って動かすためのツール、Kubernetesは多数のコンテナを複数のサーバーにまたがって配置・監視・再起動するためのオーケストレーターです。ローカル開発環境やチームの検証環境ならDocker Composeで足り、Kubernetesが必要になるのは本番で台数を増やして冗長化する段階からです。


まとめ:動いたことを自分で言い切れる状態にする

Docker Composeでの環境構築は、compose.yaml を書くところより「動いていることをどう確認するか」で差が付きます。この記事の要点は4つです。

  1. ファイル名は compose.yaml、コマンドは docker compose(スペース区切り)、version: の行は書かない
  2. Apple Siliconではイメージのarm64対応を確認する。mysql:5.7 は動かない
  3. 判定はSTATUSが Up、PORTSに 0.0.0.0: が付く、HTTPステータス200、SHOW DATABASES; が返る、の4点
  4. db に名前付きvolumeを付け、down → up でデータが残るところまで確認して完了とする

まずは docker-php という空のディレクトリを1つ作り、この記事のcompose.yamlをそのまま貼って docker compose up -ddocker compose ps の2コマンドを打ってみてください。PORTSに 0.0.0.0:8080->80/tcp が出れば、そこから先は自分で進められます。

サーバーサイドの全体像を整理したい方はこちら

PHPとMySQLをPDOでつなぐ実装はこちら

WordPress制作の全体像はこちら