PHPサイトをローカルで動かすDocker環境の作り方(Nginx+PHP-FPM+MySQL)
Web制作の勉強をしていると、こういう場面が出てきます。
コードを書いたけれど、サーバーにアップロードしないと動作確認ができない。
データベースを使うページを作ったのに、ローカルで確認する方法がわからない。
PHPファイルをブラウザで開いても、コードがそのまま表示されてしまう。
引き継いだ案件のDocker設定が、そもそも何なのかわからない。
私も同じところで止まっていました。
これらを解決してくれるのがDockerです。
Dockerを使うと、自分のパソコンの中にサーバー環境を作ることができて、本番に近い状態で開発できます。
今回は実際の案件でもよく見かける構成として、
- Nginx(Webサーバー)
- PHP-FPM(PHP実行環境)
- MySQL(データベース)
- phpMyAdmin(DB管理画面)
を使った環境の作り方をまとめていきます。
なお、ここではデータベースを使うPHPサイトそのもののファイルの書き方は説明していません。そちらは別の記事にコード例つきでまとめているので、あわせて置いておきます。

Dockerとは何なのか
Dockerは、ざっくり言うとパソコンの中に小さなサーバーを作る仕組みです。
ふつう、PHPサイトを動かすにはレンタルサーバーが必要です。
でも開発中に、修正するたびにFTPでアップロードして確認、を繰り返すのは大変です。
【Dockerがない場合】
修正 → FTPアップロード → 確認
【Dockerがある場合】
修正 → 保存 → ブラウザ更新
Dockerを使うと、自分のパソコンの中にレンタルサーバーとほぼ同じ環境を作って、ローカルで動作確認ができるようになります。
ApacheとNginxの違い
ここが、初心者が最初に混乱しやすいところだと思います。
私も、訳もわからないまま1年くらい使っていました…
Dockerを設定する前に、Apacheの環境を作るのか、Nginxの環境を作るのかを選ぶ必要があります。
既存サイトの場合は、どちらが使われているかに合わせることになります。
Webサーバーには、主に
- Apache
- Nginx
があります。
どちらも、ブラウザからのアクセスを受け取る受付係です。
ブラウザ
↓
Apache または Nginx
↓
PHP
↓
MySQL
Apache
Apacheは、Webサーバーの役割とPHPの実行を、比較的シンプルにこなせます。
情報が多くて初心者向けで、.htaccess が使えて、設定ファイルもわりとわかりやすいです。
小〜中規模のサイトやLP制作でもよく使われています。
Nginx
NginxはPHPを直接実行するのではなく、PHP-FPMという別の仕組みと連携してPHPを動かします。
登場人物が1人増えるような感じです。
高速で軽量で、大規模サイトでも採用されることが多く、Laravelなどの実務案件でもよく見かけます。
Apacheより少しだけ設定が複雑です。
データベースを使うならNginxじゃないとダメ?
私は最初、データベースは複雑そうだからNginxじゃないとダメなのかと思っていました。
でも、そんなルールはありませんでした。
「データベースを使うならNginx、使わないならApache」
と思われがちですが(私は思っていました…)、そういう決まりはないようです。
ApacheでもMySQLを使ったサイトは作れますし、Nginxでもデータベースを使わないPHPサイトは作れます。
結局どちらを選ぶのか
新規制作の場合は、自分でApacheかNginxを選べます。
一方で、既存サイトの修正や引き継ぎ案件では、すでに使われている環境に合わせるのが一般的です。
既存の設定ファイルを見ると、どちらの環境かを判断できます。
nginx.conf がある → Nginx
apache-config.conf がある → Apache
Dockerfile に FROM nginx と書かれている → Nginx
Dockerfile に FROM php:8.x-apache → Apache
環境構築の前にやっておきたいこと
この記事を見ている方は、これから環境構築のためにファイルを作ろうとしている方が多いかと思います。
作業を始める前にやっておきたいのが、機密情報の入ったファイルを守っておくことです。
GitHubを使っている場合は、パスワードなどが入っているファイルを.gitignoreしておきます。
GitHubに公開すると、第三者に見られてしまう可能性があるからです。
また、AIに読み込ませないようにする設定もしておきたいところです。
機密情報を読み込ませない方法は、別の記事にまとめているので置いておきます。

Claude Codeを使っている場合・・・

今回作る構成と、それぞれの役割
今回作る環境は、こういう構成です。
ブラウザ
↓
Nginx
↓
PHP-FPM
↓
MySQL
phpMyAdmin
↓
MySQL
それぞれの役割は、こう分かれています。
- Nginx → ブラウザからのアクセスを受け取る
- PHP-FPM → PHPコードを実行する
- MySQL → データを保存する
- phpMyAdmin → DBを管理する
ファイル構成
今回作るファイルの全体像です。
プロジェクト/
├── docker-compose.yml ← ①「監督」全体をまとめる指示書
├── docker/
│ ├── Dockerfile ← ②(1) Nginx(Webサーバー)の設計図
│ ├── nginx.conf ← ③ Nginxの設定ファイル
│ └── php/
│ └── Dockerfile ← ②(2) PHP の設計図
└── sample/
└── public/ ← 実際のWebサイトのファイル
なぜファイルの場所がバラバラなのか
docker-compose.yml がルート(一番上)にあるのは、全体の監督にあたるファイルだからです。
プロジェクトの一番上に置くのがルールになっています。
Dockerfile が docker/ フォルダの中にあるのは、各サーバーの設計図をDocker関連としてまとめて整理するためです。
NginxとPHPで設計図が違うので、PHPの設計図はさらに docker/php/ フォルダの中に入れています。
① docker-compose.yml ― 全体の「監督」
docker-compose.yml は、どのサーバーを何台立てるかを書いた指示書です。
映画の撮影に例えると、こんなイメージです。
docker-compose.yml = 映画の監督
├── web(Nginx) = カメラマン(ページを見せる係)
├── php(PHP) = 役者(PHPコードを実行する係)
├── db(MySQL) = 台本(データを保存する係)
└── phpmyadmin = 台本エディタ(データを見たり編集する画面)
まず全体のコードです。
services:
web:
build:
context: .
dockerfile: docker/Dockerfile
ports:
- "80:80"
volumes:
- ./sample/public:/var/www/html/public
depends_on:
- php
php:
build:
context: .
dockerfile: docker/php/Dockerfile
volumes:
- ./sample/public:/var/www/html/public
depends_on:
- db
db:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: sample_database
MYSQL_USER: sample_user
MYSQL_PASSWORD: your_password
volumes:
- db_data:/var/lib/mysql
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- "8080:80"
environment:
PMA_HOST: db
PMA_USER: root
PMA_PASSWORD: root
depends_on:
- db
volumes:
db_data:
ここから1つずつ見ていきます。
1. web(Nginx ― Webサーバー)
web:
build:
context: .
dockerfile: docker/Dockerfile
ports:
- "80:80"
volumes:
- ./sample/public:/var/www/html/public
depends_on:
- php
設定の意味はこうなっています。
- build → docker/Dockerfile を設計図として使ってサーバーを作る
- ports “80:80” → localhost にアクセスするとこのサーバーにつながる
- volumes → 自分のPCの public/ を、サーバーの中から見えるようにする
- depends_on: php → PHPサーバーが起動してから、このサーバーを起動する
volumesとは
volumesは、自分のパソコンのフォルダとDocker内のサーバーのフォルダを同期させる仕組みです。
自分のPC Dockerの中
public/index.php ← 同期 → /var/www/html/public/index.php
これがあると、自分のPCでファイルを編集したときに、Docker内にも即座に反映されます。
いちいちコピーする必要がありません。
パスの書き方について、sample はフォルダ名の例です。
実際には自分のプロジェクトのフォルダ名に変えることになります。
たとえば website/public/ という構成なら、こうなります。
volumes:
- ./website/public:/var/www/html/public
depends_on
depends_onは、サーバーの起動順序を指定するものです。
Nginxが先に起動してしまうと、PHPサーバーがまだ動いていなくてエラーになります。
depends_on: php と書くことで、PHPが先に起動してからNginxを起動する、という順番になります。
2. php(PHP-FPM)
php:
build:
context: .
dockerfile: docker/php/Dockerfile
volumes:
- ./sample/public:/var/www/html/public
depends_on:
- db
PHPコードを実行するためのサーバーです。
NginxがPHPファイルへのリクエストを受け取ると、このPHPサーバーに転送して処理してもらいます。
depends_on: db と書いてあるので、データベースが起動してからPHPが起動します。
起動順序はこうなります。
db(MySQL)→ php(PHP)→ web(Nginx)
3. db(MySQL ― データベース)
db:
image: mysql:8.0
ports:
- "3306:3306"
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: sample_database
MYSQL_USER: sample_user
MYSQL_PASSWORD: your_password
volumes:
- db_data:/var/lib/mysql
設定の意味はこうです。
- image: mysql:8.0 → MySQLの既製品をそのまま使う
- ports “3306:3306” → 3306番ポートでアクセスできる
- environment → 初回起動時にDB・ユーザー・パスワードを自動作成する
- volumes: db_data → Dockerを停止してもデータが消えないように保存する
build と image の違い
こう分かれています。
build → Dockerfileという設計図から自分でカスタムして作る
image → 既製品をそのまま使う(カスタム不要なとき)
MySQLはそのまま使えるので image、PHPはDB接続用の拡張機能を追加する必要があるので build を使っています。
environment
サーバーに渡す初期設定です。
MySQLの場合、初回起動時にここに書いた値でデータベースやユーザーが自動作成されます。
- MYSQL_ROOT_PASSWORD → 管理者(root)のパスワード
- MYSQL_DATABASE → 自動作成するデータベース名
- MYSQL_USER → 自動作成するユーザー名(root は既に存在するため使えない)
- MYSQL_PASSWORD → そのユーザーのパスワード
ローカルだけならシンプルでもいい
ローカル開発は自分しか使わないので、記載はシンプルでも大丈夫です。
db:
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: sample_database
MYSQL_USER と MYSQL_PASSWORD を省略して、root だけで運用するパターンです。
この場合、パスワードを書いている機密情報ファイルも root で接続します。
$db_user = 'root';
$db_pass = 'root';
Q. 独自のDB名やユーザー名を設定する必要はあるのか
独自のデータベース名・ユーザー名・パスワードを設定しておくと、「これはLP用のデータベースだな」というふうに自分で管理しやすくなります。
Q. root とは何か
MySQLの管理者アカウントです。
会社で例えると、root が社長、MYSQL_USER が一般社員、というイメージです。
Q. Docker起動時に何が起きているのか
たとえば、こう書いたとします。
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: sample_database
MYSQL_USER: sample_user
MYSQL_PASSWORD: 0000
この場合、Dockerは初回起動時に、次のことを自動でやってくれます。
管理者(root)を作る ↓ データベース(sample_database)を作る ↓ ユーザー(sample_user)を作る ↓ パスワード(0000)を設定する ↓ sample_database を使う権限を与える
Q. phpMyAdminだけ root なのはなぜか
docker-compose.yml の phpmyadmin のところが、PMA_USER: root、PMA_PASSWORD: root になっています。
phpMyAdminはデータベースの管理画面です。
管理者としてログインすることが多く、管理者であればDB作成・DB削除・ユーザー管理などができるからです。
Q. Dockerで使うパスワードは本番と関係あるのか
関係ありません。
DockerのMySQLとレンタルサーバーのMySQLは、完全に別物です。
同じデータベース名にしても、自動で同期されることはありません。
4. phpmyadmin(DB管理画面)
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- "8080:80"
environment:
PMA_HOST: db
PMA_USER: root
PMA_PASSWORD: root
depends_on:
- db
ブラウザで localhost:8080 にアクセスすると、phpMyAdminの画面が開きます。
- PMA_HOST: db → 上で定義した db サービスに接続する
- PMA_USER: root → root ユーザーでログインする
- PMA_PASSWORD: root → root のパスワード
PMA_HOST: db の db は、docker-compose.yml 内で定義したサービス名です。
Docker内では、サービス名がそのままホスト名(接続先の名前)として使えます。
5. volumes(データ保存領域)
volumes:
db_data:
db_data という名前のデータ保存領域を作ります。
これがないと、docker compose down で停止するたびに、データベースの中身が全部消えてしまいます。
db_data に保存しておくことで、停止してもデータが残ります。
② Dockerfile ― 各サーバーの「設計図」
②(1) docker/Dockerfile(Nginx用)
FROM nginx:alpine
COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf
WORKDIR /var/www/html
COPY ./sample/public /var/www/html/public
RUN chown -R nginx:nginx /var/www/html && \
chmod -R 755 /var/www/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
それぞれの意味はこうです。
- FROM nginx:alpine → Nginxの軽量版を土台にする(家の「基礎」)
- COPY … nginx.conf → 自分で作ったNginxの設定ファイルをコピーする
- WORKDIR → 作業フォルダを設定する
- COPY … public → Webサイトのファイルをサーバーにコピーする
- RUN chown … chmod → ファイルの権限を設定する(セキュリティ対策)
- EXPOSE 80 → 80番ポートを開ける
- CMD […] → Nginxを起動する
②(2) docker/php/Dockerfile(PHP用)
FROM php:8.x-fpm
RUN docker-php-ext-install pdo pdo_mysql mysqli
RUN apt-get update && apt-get install -y libonig-dev && docker-php-ext-install mbstring
WORKDIR /var/www/html
それぞれの意味はこうです。
- FROM php:8.x-fpm → PHP を土台にする(8.x は実際のバージョン番号に置き換える)
- RUN … pdo pdo_mysql mysqli → MySQLに接続するための拡張機能を追加する
- RUN … mbstring → 日本語を扱う関数(mb_strlen 等)を使えるようにする
- WORKDIR → 作業フォルダを設定する
ここの 8.x は、そのままでは使えません。8.2 や 8.4 のように、実際のバージョン番号に置き換える必要があります。
FROM php:8.x-fpm のバージョンは本番に合わせる
ここは、本番環境に合わせるのが基本のようです。
リモートサーバーが 8.2 なら Docker も 8.2
8.4 なら Docker も 8.4、という形です。
ローカルで動いても本番で動かない、ということが起きる可能性があるからです。
【新しい書き方を使った場合】
ローカル(新しいPHP) → 動く
本番(古いPHP) → エラーになる
【古い書き方をしていた場合】
ローカル(古いPHP) → 動く
本番(新しいPHP) → エラーになる
※この記事のコードでは 8.x と書いていますが、PHPのバージョンはその時々で状況が変わります。作業する時点で本番のバージョンを確認して、その数字に合わせるのがよいと思います。
PHPのバージョンは定期的に入れ替わり、サポートが終わる時期も決まっています。たとえば PHP 8.2 は2024年末に通常サポート(バグ修正)が終わり、その後はセキュリティ修正のみのフェーズに入りました。新しく作る場合は、本番サーバーで使えるバージョンを確認したうえで、8.x の部分をその数字に書き換えるのがよいと思います。
リモートサーバーのPHPバージョンの確認方法
レンタルサーバーの管理画面から確認できます。
または、一時的にこういうPHPファイルをサーバーにアップして確認する方法もあります。
<?php
phpinfo();
ブラウザでアクセスすると、PHPバージョンや設定が全部表示されます。
ただし、確認したら必ず削除する必要があります。セキュリティ上、公開したままにしてはいけないファイルです。
なぜPHPにだけ追加インストールが必要なのか
PHPの公式Dockerイメージには、データベース接続機能が最初から入っていません。
pdo_mysql を追加しないと、new PDO(…) が「そんな機能はありません」とエラーになります。
同じように、mbstring を追加しないと mb_strlen() などが使えません。
③ nginx.conf ― Nginxの設定ファイル
server {
listen 80;
server_name localhost;
root /var/www/html/public;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass php:9000;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
それぞれの意味はこうです。
- listen 80 → 80番ポートで待ち受ける
- root /var/www/html/public → Webサイトのファイルがある場所
- index index.php index.html → ファイル名がないとき、最初に開くファイル
- location ~ \.php$ → .php で終わるファイルへのリクエストの場合
- fastcgi_pass php:9000 → PHPサーバーに処理を転送する
fastcgi_pass php:9000 の php は、docker-compose.yml のPHPサービス名です。
Nginxは自分ではPHPを実行できないので、PHPファイルへのリクエストが来たら、PHPサーバーに代わりに処理してもらっています。
4つのサーバーの連携図

ブラウザで localhost にアクセス
↓
web (Nginx) HTMLや画像 → そのまま返す
ポート: 80 PHPファイル → phpサーバーに転送
↓(.phpファイルの場合)
php (PHP-FPM) PHPコードを実行する
ポート: 9000 DBが必要なら db に問い合わせる
↓(データが必要な場合)
db (MySQL) データを保存・返す
ポート: 3306
↑
phpmyadmin DBの中身を見たり編集する管理画面
ポート: 8080 (ブラウザからアクセスして管理)
基本的な使い方
起動する
docker compose up --build
–build をつけると、Dockerfileの変更が反映されます。
初回や、設定を変更した後はつけておくと安心です。
ブラウザで確認する
今回の場合
↓
http://localhost → Webサイト
http://localhost:8080 → phpMyAdmin(DB管理画面)
停止する
docker compose down
データベースの中身は db_data に保存されているので、停止しても消えません。
データも含めて全部消したい場合
docker compose down -v
-v をつけると、保存領域(db_data)も削除されます。
次に起動したときは、データベースが空の状態に戻ります。
リモートのデータをローカルに持ってくる方法
リモートサーバー(本番)とローカル(Docker)のデータベースは、完全に別物です。
リモートでphpMyAdminにデータを入れても、ローカルには自動では反映されません。
リモートのデータをローカルで使いたい場合は、
エクスポート(書き出し)とインポート(読み込み)
を行います。
STEP 1: リモートのphpMyAdminでエクスポート
① リモートサーバーのphpMyAdminにログイン
② コピーしたいテーブルを選択
③ 上部メニューの「エクスポート」をクリック
④ 「実行」をクリック → .sql ファイルがダウンロードされる
STEP 2: ローカルのphpMyAdminでインポート
① docker compose up でDockerを起動 ② ブラウザで localhost:8080 にアクセス ③ 上部メニューの「インポート」をクリック ④ 「ファイルを選択」でSTEP 1の .sql ファイルを選ぶ ⑤ 「実行」をクリック → データが読み込まれる
リモートのphpMyAdmin
→ エクスポート → .sql ファイルを保存
↓
ローカルのphpMyAdmin (localhost:8080)
→ インポート → .sql ファイルを読み込む
データが入った状態でもう一度インポートするとき
データが入っている状態でもう一度インポートすると、エラーが出ます。
その対応は別の記事にまとめているので、置いておきます。

機密情報ファイルのホスト名について
ここでの機密情報ファイルとは、データベースに接続する際にパスワードを書いてあるファイルのことです。
各ファイルの説明は別記事にまとめています↓

PHPからデータベースに接続するとき、本番環境とDocker環境では接続先のホスト名が変わります。
パスワードを書いているファイルの、ホスト名の部分を書き換える必要があります。
// 本番(リモートサーバー)の場合
$db_host = 'localhost';
// Docker(ローカル開発)の場合
$db_host = 'db';
Docker環境では、接続先が localhost ではなく db になります。
これは、docker-compose.yml で定義したサービス名が、そのままホスト名になるためです。
環境を切り替えるときは、この $db_host を変更することになります。
まとめ
全体の流れとしては、こうなっています。
Webサイトを見る ↓ Nginxが受け取る ↓ PHPなら PHP-FPM へ渡す ↓ DBが必要なら MySQL へ問い合わせる ↓ 結果をブラウザへ返す
それぞれの役割を並べると、
という関係でした。
ファイルの役割も、あらためて並べるとこうなります。
- docker-compose.yml → 全体の指示書(この4つのサーバーを立ててね)
- docker/Dockerfile → Nginxの設計図(Webサーバーはこう作ってね)
- docker/php/Dockerfile → PHPの設計図(PHPにDB接続機能を追加してね)
- docker/nginx.conf → Nginxの設定(PHPファイルはPHPサーバーに転送してね)
案件を引き継ぐ場合は、まず
を確認すると環境を把握しやすいです。
Dockerを使えるようになると、本番とほぼ同じ環境を手元で再現できて、修正してすぐ確認、という開発サイクルになります。
私自身、避けていたころに比べると、開発がずいぶん楽になりました。同じところで止まっている方に届いたらうれしいです。
ここまで読んでくださり、ありがとうございました。


