いつもブログをご覧くださり、ありがとうございます。
前回の記事では、UbuntuにDocker Engineをインストールし、Docker上でPHPを実行しました。
最終的にはDocker Composeを使ってPHP 8.4の開発環境を起動し、LAN内のMacからブラウザでPHPへアクセスできるところまで確認しました。
今回はその続きとして、Docker ComposeにMySQL 8.4を追加します。
最終的には、
Mac / Windows
│
│ HTTP
▼
Ubuntu Server
│
▼
Docker Compose
│
├── PHP 8.4
│ │
│ │ PDO
│ ▼
└── MySQL 8.4
│
└── sample_db
という環境を構築し、MySQLに保存したデータをPHPから取得してブラウザに表示してみます。今回も自分自身の備忘録を兼ねて、実際に行った手順をまとめます。
目次
この記事について
本記事に掲載しているIPアドレス、ユーザー名、データベース名、パスワードなどは説明用の値です。
UbuntuサーバーのIPアドレスは、
192.168.1.100
として説明します。
実際に試す場合は、ご自身の環境に合わせて読み替えてください。
今回使用している主な環境は以下です。
Ubuntu 24.04.5 LTS
PHP 8.4
MySQL 8.4
Docker Engine
Docker Compose
前回作成したディレクトリは、
~/workspace/php-test/
です。
Docker ComposeにMySQLを追加する
まず作業ディレクトリへ移動します。
cd ~/workspace/php-test
前回作成した compose.yaml に、MySQLの設定を追加します。
nano compose.yaml
今回は以下のようにしました。
services:
php:
image: php:8.4-cli
ports:
- "8001:8000"
volumes:
- .:/app
working_dir: /app
command: php -S 0.0.0.0:8000
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: sample_db
MYSQL_USER: sample_user
MYSQL_PASSWORD: sample_password
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
今回新しく追加したのが db サービスです。
db:
image: mysql:8.4
これによって、PHPとは別にMySQL 8.4のコンテナが起動します。
また、
MYSQL_DATABASE: sample_db
MYSQL_USER: sample_user
MYSQL_PASSWORD: sample_password
として、検証用データベースとユーザーを用意します。
今回はローカル環境で仕組みを確認することを目的として、分かりやすいパスワードを直接記述しています。本番環境ではこのようなパスワードを使用せず、認証情報の管理方法も別途検討する必要があります。
MySQLのデータをDocker Volumeへ保存する
MySQLでは次の設定も追加しています。
volumes:
- mysql_data:/var/lib/mysql
そして compose.yaml の最後に、
volumes:
mysql_data:
と記述します。
これによって、MySQLのデータをコンテナそのものではなく、Dockerの名前付きボリュームへ保存します。
イメージとしては、
MySQLコンテナ
│
│ /var/lib/mysql
▼
mysql_data
(Docker Volume)
となります。
コンテナを削除・再作成しても、このボリュームを削除しない限りデータを保持できます。
Docker Composeの設定を確認する
設定を書き換えたら、
sudo docker compose config
を実行しました。
このコマンドを使うと、compose.yaml の設定内容を確認できます。
実際の作業では、編集途中に、
yaml: construct errors:
mapping key "volumes" already defined
というエラーにも遭遇しました。
YAMLはインデントや階層が重要です。
今回の構造は、
services:
├── php:
│ └── volumes:
│
└── db:
└── volumes:
volumes:
└── mysql_data:
となります。
db の中にある volumes: と、ファイル末尾にある volumes: は役割も階層も異なります。
修正後、
sudo docker compose config
でエラーが表示されなくなったことを確認してから作業を続けました。
PHPとMySQLを起動する
Docker Composeを起動します。
sudo docker compose up -d
状態を確認します。
sudo docker compose ps
PHPとMySQLの両方が起動していればOKです。
MySQLのログは、
sudo docker compose logs db
で確認できます。
正常に起動すると、ログの中に、
ready for connections
というメッセージが表示されます。
MySQLへログインする
続いて、MySQLコンテナへ接続してみます。
sudo docker compose exec db \
mysql -u sample_user -p sample_db
パスワードを入力すると、
mysql>
というプロンプトが表示されます。
データベースを確認します。
SHOW DATABASES;
今回の環境では、
+--------------------+
| Database |
+--------------------+
| information_schema |
| performance_schema |
| sample_db |
+--------------------+
と表示されました。
sample_db が作成されています。
終了するときは、
exit;
です。
PHPからMySQLへ接続する準備
次はPHPからMySQLへ接続します。
そのためにPHPのPDO MySQL拡張を追加します。
今回は、PHP公式Dockerイメージをそのまま利用するのではなく、Dockerfile を作成して必要な機能を追加してみました。
nano Dockerfile
以下を記述します。
FROM php:8.4-cli
RUN docker-php-ext-install pdo_mysql
これで、
PHP公式イメージ
↓
pdo_mysqlを追加
↓
今回のPHP用イメージ
という形になります。
compose.yamlをDockerfileに対応させる
PHP部分も変更します。
これまでの、
php:
image: php:8.4-cli
から、
php:
build: .
へ変更します。
最終的な compose.yaml は以下のようになりました。
services:
php:
build: .
ports:
- "8001:8000"
volumes:
- .:/app
working_dir: /app
command: php -S 0.0.0.0:8000
db:
image: mysql:8.4
environment:
MYSQL_ROOT_PASSWORD: rootpassword
MYSQL_DATABASE: sample_db
MYSQL_USER: sample_user
MYSQL_PASSWORD: sample_password
volumes:
- mysql_data:/var/lib/mysql
volumes:
mysql_data:
現在のファイル構成はおおよそ、
php-test/
├── Dockerfile
├── compose.yaml
└── hello.php
となります。
PHPイメージをビルドする
一度コンテナを停止します。
sudo docker compose down
続いて、
sudo docker compose build
を実行します。
ビルドが完了したら、
sudo docker compose up -d
で再度起動します。
PDO MySQLが利用できるようになったか確認しました。
sudo docker compose exec php php -m | grep pdo_mysql
結果、
pdo_mysql
と表示されました。
これでPHPからMySQLへ接続する準備ができました。
PHPからMySQLへ接続する
接続確認用に db-test.php を作成します。
nano db-test.php
以下のPHPを書きました。
<?php
$host = 'db';
$dbname = 'sample_db';
$user = 'sample_user';
$password = 'sample_password';
try {
$pdo = new PDO(
"mysql:host={$host};dbname={$dbname};charset=utf8mb4",
$user,
$password
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
echo "MySQLへの接続に成功しました!";
} catch (PDOException $e) {
echo "MySQLへの接続に失敗しました。";
echo "<br>";
echo $e->getMessage();
}
Macのブラウザから、
http://192.168.1.100:8001/db-test.php
へアクセスします。
画面に、
MySQLへの接続に成功しました!
と表示されました。
これで、
PHP
↓
PDO
↓
MySQL
の接続が確認できました。
なぜMySQLのホスト名が「db」なのか?
今回、PHPでは、
$host = 'db';
としています。
ここはDocker Composeを理解するうえで重要なポイントでした。
compose.yaml では、
services:
php:
...
db:
image: mysql:8.4
と定義しています。
Docker Composeでは、同じComposeネットワーク内のサービス同士をサービス名で参照できます。
そのためPHPコンテナからMySQLへ接続するときは、
localhost
ではなく、
db
を指定します。
構成としては、
PHPコンテナ
│
│ host=db
▼
Docker Compose Network
│
▼
MySQLコンテナ
となります。
PHPコンテナで localhost と指定すると、PHPコンテナ自身を指すことになるため、別コンテナで動いているMySQLには接続できません。
MySQLにテーブルを作成する
接続できたので、実際にデータを登録してみます。
MySQLへログインします。
sudo docker compose exec db \
mysql -u sample_user -p sample_db
今回は簡単な users テーブルを作りました。
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) NOT NULL
);
確認します。
SHOW TABLES;
続いてサンプルデータを2件登録します。
INSERT INTO users (name, email)
VALUES
('Taro', 'taro@example.com'),
('Hanako', 'hanako@example.com');
確認します。
SELECT * FROM users;
結果は、
+----+--------+--------------------+
| id | name | email |
+----+--------+--------------------+
| 1 | Taro | taro@example.com |
| 2 | Hanako | hanako@example.com |
+----+--------+--------------------+
となりました。
PHPからMySQLのデータを取得する
今度はPHPから users テーブルをSELECTして、ブラウザへ表示してみます。
nano users.php
次のコードを作成しました。
<?php
$host = 'db';
$dbname = 'sample_db';
$user = 'sample_user';
$password = 'sample_password';
try {
$pdo = new PDO(
"mysql:host={$host};dbname={$dbname};charset=utf8mb4",
$user,
$password
);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$stmt = $pdo->query('SELECT * FROM users');
$users = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $e) {
exit('データベース接続エラー: ' . $e->getMessage());
}
?>
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>ユーザー一覧</title>
</head>
<body>
<h1>ユーザー一覧</h1>
<table border="1">
<tr>
<th>ID</th>
<th>名前</th>
<th>メールアドレス</th>
</tr>
<?php foreach ($users as $user): ?>
<tr>
<td><?= htmlspecialchars($user['id']) ?></td>
<td><?= htmlspecialchars($user['name']) ?></td>
<td><?= htmlspecialchars($user['email']) ?></td>
</tr>
<?php endforeach; ?>
</table>
</body>
</html>
ブラウザから、
http://192.168.1.100:8001/users.php
へアクセスします。
すると、
| ID | 名前 | メールアドレス |
|---|---|---|
| 1 | Taro | taro@example.com |
| 2 | Hanako | hanako@example.com |
と表示されました。
これで、
ブラウザ
↓
PHP
↓
PDO
↓
MySQL
↓
usersテーブル
↓
SELECT
↓
PHPでHTMLを生成
↓
ブラウザへ表示
という一連の流れを確認できました。
「Connection refused」が発生した
今回の検証途中で、users.php にアクセスすると、
データベース接続エラー:
SQLSTATE[HY000] [2002] Connection refused
というエラーが発生しました。
MySQLのログを確認しました。
sudo docker compose logs db
すると、MySQL自体に異常があるわけではなく、
InnoDB initialization has started.
...
InnoDB initialization has ended.
...
ready for connections.
という流れで起動していました。
今回の環境では、MySQLコンテナの起動開始から実際に接続可能になるまで数秒かかっていました。
つまり、
Docker Compose起動
│
├── PHP → 先に利用可能
│
└── MySQL
↓
起動処理
↓
InnoDB初期化
↓
ready for connections
という時間差があり、その間にPHPから接続しようとしたことで Connection refused が発生したと考えられます。
MySQLのログで、
ready for connections
を確認した後、ブラウザを再読み込みすると正常に表示されました。
今後はDocker Composeの healthcheck や depends_on を利用して、MySQLが利用可能になってからPHPを起動する方法も試してみたいと思います。
Docker Volumeによるデータの永続化
今回、
volumes:
mysql_data:
という名前付きボリュームを作成しました。
Dockerコンテナは作り直すことがありますが、データベースまで毎回消えてしまうと困ります。
そこでMySQLのデータを、
MySQL Container
│
│ /var/lib/mysql
▼
Docker Volume
php-test_mysql_data
へ保存しています。
通常の、
sudo docker compose down
では、この名前付きボリュームは削除されません。
そのため、
sudo docker compose down
sudo docker compose up -d
としてコンテナを再作成しても、MySQLのデータを引き続き利用できます。
一方、
sudo docker compose down -v
ではボリュームも削除対象になります。
データベースを扱う場合は、この違いを理解しておくことが重要です。
まとめ
今回は、前回構築したDocker ComposeのPHP環境にMySQLを追加しました。
最終的に、
Ubuntu Server
└── Docker Compose
│
├── PHP 8.4
│ ├── PDO
│ ├── pdo_mysql
│ └── users.php
│
└── MySQL 8.4
└── sample_db
└── users
という開発環境を構築できました。
実際に試してみることで、
PHPコンテナとMySQLコンテナは別々に動作しており、Docker Composeのネットワークを通じて相互に通信している
という仕組みも理解しやすくなりました。
また今回は、
- Docker Composeで複数コンテナを管理する
- Dockerfileを使ってPHPイメージをカスタマイズする
pdo_mysqlを追加する- PHPからMySQLへPDOで接続する
- Composeのサービス名
dbをホスト名として利用する - MySQLへデータを登録する
- PHPからデータを取得する
- Docker Volumeでデータを永続化する
- MySQL起動直後の
Connection refusedを確認する
といったことを実際に試すことができました。
今回のようにシンプルなPHP + MySQL環境を自分で構築してみることで、普段利用しているWordPressの裏側の仕組みについても理解を深めることができました。
今後は、今回の環境をもう少し改善しながら、LaravelやWordPressをDocker上で動かす開発環境にも挑戦してみたいと思います。

