DockerでPHP8.2＋MySQL8.0の環境を構築する

## はじめに
この記事では、DockerでPHP＋Apache＋MySQLの環境を構築し、DBから取得したデータをWebサーバー上で表示するまでの手順を紹介する。

環境情報と、最終的なディレクトリ構造は以下の通り。
appディレクトリでPHP＋Apacheのコンテナを、mysqlディレクトリでMySQLのコンテナを起動し、PHP側からMySQLに接続するイメージ。
|  |環境情報  |
|---|---|
|PC  |Apple M1 Pro  |
|OS  |Sonoma 14.4  |
|Docker  |27.2  |
|PHP  |8.2  |
|Apache  |2.4  |
|MySQL  |8.0  |

```terminal
docker_practice $ tree .
.
├── app
│   ├── Dockerfile
│   └── src
│       └── index.php
└── mysql
    ├── Dockerfile
    ├── data
    └── init.sql

5 directories, 4 files
```

Docker Desktopのインストールや、PHP＋Apacheの基本的な環境構築は、「[DockerでPHP+Apacheの環境を構築する](https://qiita.com/kwtck/items/03509707f0d84a1810d0)」で紹介している。

## 1. Dockerプロジェクトを作成する
(1) Docker環境を構築するためのプロジェクトディレクトリを作成する。
```terminal
$ mkdir docker_practice
```

(2) プロジェクトディレクトリ配下にappディレクトリとmysqlディレクトリを作成する。
```terminal
$ cd docker_practice
docker_practice $ mkdir app mysql
```

(3) 各ディレクトリ配下に、必要なディレクトリ・ファイルを作成する
```terminal
docker_practice $ mkdir app/src && touch app/Dockerfile app/src/index.php
docker_practice $ mkdir mysql/data && touch mysql/Dockerfile mysql/init.sql
```
- `app/src/index.php`：ドキュメントルートにコピーすることで、Webサーバーにアクセスした際にレスポンスとして返される
- `mysql/data`：コンテナが生成するMySQLデータをホストマシン側に保存し永続化するためのディレクトリ
- `mysql/init.sql`：コンテナ起動時に実行するSQLファイルで、`docker-entrypoint-initdb.d`配下に置くことで、コンテナの初回起動時に自動で実行される


## 2. MySQL側のDockerfileを編集する
```dockerfile:mysql/Dockerfile
# イメージを指定
FROM mysql:8.0

# init.sqlをデータベースの初期化時に実行する
COPY init.sql /docker-entrypoint-initdb.d/init.sql

# 日本語を使用できるようにする
RUN microdnf update -y \
    && microdnf install -y glibc-locale-source \
    && localedef -i ja_JP -c -f UTF-8 -A /usr/share/locale/locale.alias ja_JP.UTF-8

# 環境変数を設定
ENV MYSQL_ROOT_PASSWORD=root
ENV MYSQL_DATABASE=docker_db
ENV MYSQL_USER=test
ENV MYSQL_PASSWORD=test

ENV LANG ja_JP.UTF-8
ENV LC_ALL ja_JP.UTF-8

# dataディレクトリを/var/lib/mysqlにマウントしデータを永続化する
VOLUME ./data:/var/lib/mysql
```

## 3. init.sqlを編集する
```sql:mysql/init.sql
CREATE TABLE docker_db.users (
    id INT NOT NULL,
    name VARCHAR(20) NOT NULL,
    PRIMARY KEY (id)
);

INSERT INTO `users` VALUES (1, 'Hoge');
INSERT INTO `users` VALUES (2, 'Fuga');
```
MySQLコンテナの初回起動時に上記が実行されることで、`docker_db`に`users`テーブルが作成され、`name`が`Hoge`と`Fuga`の2つの初期データが投入される。

## 4. PHP側のDockerfileを編集する
```dockerfile:app/Dockerfile
# イメージを指定
FROM php:8.2-apache

# MySQLに接続するためのパッケージをインストールする
RUN apt update \
  && docker-php-ext-install pdo_mysql

# src配下をドキュメントルート配下にコピーする
COPY src/ /var/www/html/
```
- `apt update`：パッケージ管理システム（apt）のパッケージリストを更新し、最新のパッケージ情報を取得する
- `&&`：前のコマンドが成功した場合にのみ、次のコマンドを実行する
- `docker-php-ext-install pdo_mysql`：PHPの拡張機能であるpdo_mysqlをインストールする

## 5. index.phpを編集する
```php:app/src/index.php
<?php

$dsn = 'mysql:dbname=docker_db;host=mysql-container;';
$user = 'test';
$password = 'test';

try {
    $pdo = new PDO($dsn, $user, $password);
    $sth = $pdo->query("SELECT name FROM users WHERE id = 1");
    $userName = $sth->fetch(PDO::FETCH_COLUMN);
    echo ("My name is $userName!");
} catch (PDOException $e) {
    print("Error:".$e->getMessage());
    exit;
}
```
これによって、`docker_db`という名前のDBに、`test`とユーザーと`test`というパスワードを使用して接続し、`users`テーブルから`id = 1`のユーザーの`name`カラムを取得した結果が表示されるようになる。

## 6. ネットワークを作成する
```terminal
docker_practice $ docker network create php-mysql-network
```
- `docker network create <name>`：コンテナ同士が相互に通信できる独立したネットワークを作成するコマンド

## 7. イメージをビルドする
Dockerfileが配置してある各ディレクトリでbuildコマンドを実行する。

```terminal
docker_practice/app $ docker build -t php-app .
docker_practice/mysql $ docker build -t php-db .
```

- `docker build`：Dockerfile とコンテキストからDockerイメージを構築するコマンド
- `-t <image_name>:<tag>`：イメージに名前とタグを付けるためのオプション
- `.`：Dockerfileが存在するディレクトリのパス（ビルドコンテキスト）

## 8. コンテナを起動する
Dockerfileが配置してある各ディレクトリでrunコマンドを実行する。

```terminal
docker_practice/app $ docker container run \
-d \
-p 8080:80 \
-v "$(pwd)"/src:/var/www/html \
--network php-mysql-network \
--name app-container \
php-app
```

```terminal
docker_practice/mysql $ docker container run \
-d \
--network php-mysql-network \
--name mysql-container \
php-db
```

- `docker container run`：Dockerコンテナを作成・起動するコマンド
- `-d`：デタッチモード（Dockerコンテナをバックグラウンドで実行するモード）にするオプション
- `-p <host_port>:<container_port>`：ホストとコンテナのポートをマッピングするためのオプション
- `-v <host_path>:<container_path>`：ホストのディレクトリやファイルをコンテナ内にマウントするためのオプション
- `--network <network_name>`：`<network_name>`に指定したネットワークにコンテナを接続するためのオプション
- `--name <container_name>`：コンテナに名前を付けるためのオプション
- `php-app`、`php-db`：使用するDockerイメージ名

## 9. Webサーバーにアクセスして表示を確認
`localhost:8080`にアクセスして、ユーザーIDが1のユーザー名を反映したメッセージが表示されていれば、Docker環境上でPHPのコンテナとMySQLのコンテナ同士の接続が成功。


<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3853295/56dbdfa0-0352-cd58-f07d-7aaa25235b8f.png" width=80%>