UbuntuでDocker ComposeでPHP 8.4 + MySQL 8.4の開発環境を構築する

, , , ,

本記事は広告が含まれる場合があります。

いつもブログをご覧くださり、ありがとうございます。

前回の記事では、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名前メールアドレス
1Tarotaro@example.com
2Hanakohanako@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上で動かす開発環境にも挑戦してみたいと思います。

お問い合わせはこちらから

南大阪を中心に活動しているシステムエンジニアです。これまでの経験から、ビジネス課題について一緒になって考え、課題解決のためにサポートさせていただきます。些細なご相談でも構いません。当方に期待することやご相談内容についてお聞かせください。

トップへ戻る