[ 備忘録 ] Docker でPHP環境を作る(PHP8,Nginx,MySQL)

## 目次
- [1 構築後のイメージ](#1-前提条件)
- [2 前提条件](#2-前提条件)
- [3 ディレクトリ構造](#3-ディレクトリ構造)
- [4 環境構築（実際の作業）](#4-環境構築)
- [5 コンテナの止め方・再起動](#5-コンテナの止め方)
- [6 各ファイルの説明](#6-各ファイルの説明)
- [7 エラー時の対処法（未完成）](#7-エラー時の対処法)
## 1. 構築後のイメージ
- localhost:8000/ でindex.php が表示される
- composer インストール済み
- データベース(MySQL)に接続できる

## 2. 前提条件
- **docker エンジンが実行済みであること**
docker DesktopでDockerエンジンを起動するのがいちばん簡単な方法ですが、企業での利用では有償になるなどDocker Desktop を使用しないでDockerエンジンを使用する方法が知りたい場合は以下の方で解説しています。
　・[Docker Desktop を使わずにDocker を使う！](https://qiita.com/h_merak/items/72de528119c442fa4ff4)
 - **linux環境であること(Macでは動作します)**
 Windowsでのwslを使用したUbuntu環境構築は以下で説明しています。
 　・[【備忘録】wsl2・Ubuntuのインストール](https://qiita.com/h_merak/items/445ebc1f6fb0c080927c)

## 3. ディレクトリ構造

```
./
├── docker
│   ├── mysql/
│   │   └── my.cnf
│   ├── nginx/
│   │   └── default.conf
│   └── php/
│       ├── Dockerfile
│       └── php.ini
├── src/
│   └── index.php
├── .env
└── docker-compose.yml
```
docker ディレクトリ配下にDocker のコンテナをビルドするのに必要なファイルが入ります。
src ディレクトリ配下にプロジェクトのソースが入ります。

## 4. 環境構築
以下はディレクトリ構成を作るためのコマンドです
環境を構築するための実行ファイルをダウンロードし、実行します。
(実行ファイルに実行権限を与え、実行します。)
プロジェクトを作りたいディレクトリに移動して実行して下さい。
```
# URLからスクリプトをダウンロードする
curl -O https://raw.githubusercontent.com/Merak-h/setup_docker_env/1c222641b8d374142809667f3a19a4e3720135af/setup_docker_php_env.sh

# ダウンロードしたスクリプトに実行権限を付与する
chmod +x setup_docker_php_env.sh

# ダウンロードしたスクリプトを実行する
./setup_docker_php_env.sh
```

上記コマンドを実行後、[http://localhost:8000/](http://localhost:8000/)にアクセスし、PHPinfoの内容(下の写真参照)が表示されれば環境構築完了です！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3530065/7e3e69d1-9d77-fcc1-8936-f71a799c94cf.png)

#

上記コマンドのみで環境構築ができますが、実行ファイルの中に記述しているコマンドを直接実行しても環境構築できます。
以下が実行ファイルの内容です。
```
mkdir docker
mkdir docker/mysql
mkdir docker/php
mkdir docker/nginx
cat << EOF > docker/mysql/my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
EOF
cat << EOF > docker/nginx/default.conf
server {
  # port 80 で listen
  # docker-compose.ymlでホストマシン上のポート8000を経由するすべてのリクエスト
  # がコンテナ内のポート80にリダイレクトするように設定済み
  listen 80;
  # ドキュメントルートを設定
  # /var/www/htmlはソースコードを配置しているフォルダ
  root /var/www/html;
  # インデックスファイルを設定
  index index.php;

  location / {
    root /var/www/html;
    index index.php;
    try_files \$uri \$uri/ /index.php?\$query_string;
  }

  location ~ [^/]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    if (!-f \$document_root\$fastcgi_script_name) {
        return 404;
    }
    # https://httpoxy.org/ の脆弱性を緩和する
    fastcgi_param HTTP_PROXY "";
    # TCPソケットを使用してPHP-FPMと通信するための設定
    fastcgi_pass app:9000; 
    # スラッシュで終わるURIの後に追加されるファイル名を設定
    fastcgi_index index.php;
    # fastcgi_paramsファイルに含まれるFastCGIパラメータの設定を読み込む
    include fastcgi_params;
    # SCRIPT_FILENAME パラメータは、PHP-FPM がスクリプト名を決定する際に使用する
    fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
  }
}
EOF
cat << EOF > docker/php/Dockerfile
# イメージを取得
FROM php:8.1.18-fpm
# 独自のphp.iniファイル(PHPの設定ファイル)を 
# コンテナ内の/usr/local/etc/php/ディレクトリにコピー
COPY php.ini /usr/local/etc/php/

# パッケージやPHPの拡張モジュールをインストールするコマンド　を実行
RUN apt-get update && apt-get install -y \
	git \
	curl \
	zip \
	unzip \
    && docker-php-ext-install pdo_mysql

# Composerのインストール
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# 作業ディレクトリを/var/wwwに設定
WORKDIR /var/www
EOF
cat << EOF > docker/php/php.ini
[PHP]
post_max_size = 100M
upload_max_filesize = 100M
variables_order = EGPCS
EOF
mkdir src
cat << EOF > src/index.php
<?php
phpinfo();
EOF
cat << EOF > docker-compose.yml
version: '3'
services:
  nginx:
    image: nginx:1.25.0
    ports:
      - 8000:80
    volumes:
      # ./srcフォルダをコンテナ内の/var/www/htmlにマウント
      - ./src:/var/www/html
      # ./docker/nginxフォルダをコンテナ内の/etc/nginx/conf.dにマウント
      - ./docker/nginx:/etc/nginx/conf.d
    # 依存関係を設定
    depends_on:
      - app
  # PHP-FPMの定義
  app:
    build:
      # Dockerfileを格納するフォルダのパス
      context: ./docker/php
      # Dockerfileのファイル名
      dockerfile: Dockerfile
    # コンテナ内で使用される環境変数を定義(SQLへのアクセス情報)
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      # ./srcフォルダをコンテナ内の/var/www/htmlにマウント
     - ./src:/var/www/html   
     # 依存関係を設定(mysqlの起動後に起動する設定)
    depends_on:
      - mysql
    # MySQLの定義
  mysql:
    # MySQL コンテナに使用するイメージを指定
    image: mysql:8.0
    # コンテナ内で使用される環境変数を定義
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      # 名前付きボリュームを MySQL コンテナに紐づける
      - mysqldata:/var/lib/mysql
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      # - ./docker/mysql/sql:/docker-entrypoint-initdb.d
    ports:
      - 3306:3306
volumes:
  # 名前付きボリュームの作成
  mysqldata:
EOF
cat << EOF > .env
MYSQL_ROOT_PASSWORD=rootPass
MYSQL_DATABASE=test-db
MYSQL_USER=testUser
MYSQL_PASSWORD=testPass
EOF
docker compose up -d --build
```

## 5. コンテナの止め方
docker のコンテナを閉じたいときは
`docker compose down`
docker のコンテナを完全に削除したい場合は、
`docker compose down --rmi all --volumes --remove-orphans`
参考資料
[《滅びの呪文》Docker Composeで作ったコンテナ、イメージ、ボリューム、ネットワークを一括完全消去する便利コマンド](https://qiita.com/suin/items/19d65e191b96a0079417)

再起動は
`docker compose up -d`
ビルドするときは
`docker compose up -d --build`
-d オプションについて...
-d オプションは、コンテナをデタッチモードで起動し、バックグラウンドで実行されるため、コンテナがバックグラウンドで動作し、ターミナルをブロックせずにプロセスが実行できます。

## 6. 各ファイルの説明
### mysql/my.cnf
```my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_general_ci
```
**my.cnfはMySQLの設定ファイルです。** 
- `character-set-server = utf8mb4`
MySQLサーバーのデフォルトの文字セットを utf8mb4 に設定しています。
`utf8mb4`は、`utf8`文字セットの拡張版で、4バイトの文字をサポートしていて、絵文字やその他の特殊文字も対応しています。
<br>

- `collation-server = utf8mb4_general_ci`
MySQLサーバーのデフォルトの照合順序（文字の比較とソートの規則）を `utf8mb4_general_ci` に設定しています。
`utf8mb4_general_ci` は、大文字小文字を区別しない照合順序で、utf8mb4 文字セット用の一般的な照合順序。
### default.conf
```default.conf
server {
  # port 80 で listen
  # docker-compose.ymlでホストマシン上のポート8000を経由するすべてのリクエスト
  # がコンテナ内のポート80にリダイレクトするように設定済み
  listen 80;
  # ドキュメントルートを設定
  # /var/www/htmlはソースコードを配置しているフォルダ
  root /var/www/html;
  # インデックスファイルを設定
  index index.php;

  location / {
    root /var/www/html;
    index index.php;
    try_files $uri $uri/ /index.php?$query_string;
  }

  location ~ [^/]\.php(/|$) {
    fastcgi_split_path_info ^(.+?\.php)(/.*)$;
    if (!-f $document_root$fastcgi_script_name) {
        return 404;
    }
    # https://httpoxy.org/ の脆弱性を緩和する
    fastcgi_param HTTP_PROXY "";
    # TCPソケットを使用してPHP-FPMと通信するための設定
    fastcgi_pass app:9000; 
    # スラッシュで終わるURIの後に追加されるファイル名を設定
    fastcgi_index index.php;
    # fastcgi_paramsファイルに含まれるFastCGIパラメータの設定を読み込む
    include fastcgi_params;
    # SCRIPT_FILENAME パラメータは、PHP-FPM がスクリプト名を決定する際に使用する
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
  }
}

```
**このファイルはNginxの設定ファイルです。**
- listen 80;: ポート80での接続を待ち受けます。つまり、HTTPリクエストを受け付けるためにNginxがポート80で動作します。

- root /var/www/html;: ドキュメントルートを /var/www/html ディレクトリに設定します。これは、クライアントがアクセスした際にサーバーが提供するコンテンツの基準ディレクトリです。

- index index.php;: ディレクトリにアクセスした際に表示されるデフォルトのファイルを index.php に設定します。つまり、ディレクトリにアクセスした際に自動的に index.php を読み込みます。

- location / { ... }: ルートパス(/)へのリクエストに対する設定を指定します。try_files ディレクティブは、リクエストされたファイルが存在しない場合に、別の場所からファイルを探すようにします。具体的には、最初に ＄uri 、次に ＄uri/ （ディレクトリ）にアクセスし、見つからない場合は /index.php?＄query_string （クエリ文字列を保持したまま）にリダイレクトします。

- location ~ [^/]\.php(/|$) { ... }: 正規表現にマッチするURIへのリクエストに対する設定を指定します。つまり、.php で終わるURIに対して、PHPスクリプトの処理を行います。fastcgi_pass ディレクティブは、PHP-FPMとの通信を行うためのソケットまたはホストとポートを指定します。また、SCRIPT_FILENAME パラメータは、PHP-FPMがスクリプトを処理する際に使用するファイルのパスを指定します。

- この設定は、Nginxを使用してPHPを処理するWebサイトを構築するための一般的な設定です。
### php/Dockerfile
```Dockerfile 
# イメージを取得
FROM php:8.1.18-fpm
# 独自のphp.iniファイル(PHPの設定ファイル)を 
# コンテナ内の/usr/local/etc/php/ディレクトリにコピー
COPY php.ini /usr/local/etc/php/

# パッケージやPHPの拡張モジュールをインストールするコマンド　を実行
RUN apt-get update && apt-get install -y 	git 	curl 	zip 	unzip     && docker-php-ext-install pdo_mysql

# Composerのインストール
RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer

# 作業ディレクトリを/var/wwwに設定
WORKDIR /var/www

```
**このDockerfileは、PHP 8.1.18のFPMイメージをベースにphpコンテナをビルドする際の設定ファイルです**

- php.ini ファイルを独自の設定でコンテナ内の /usr/local/etc/php/ ディレクトリにコピーします。
- apt-get update と apt-get install コマンドを使って、必要なパッケージとPHPの拡張モジュールをインストールします。ここでは git、curl、zip、unzip がインストールされます。また、pdo_mysql 拡張モジュールもインストールされます。
- Composerをインストールするために、公式のComposerインストールスクリプトをダウンロードし、実行します。ComposerはPHPのパッケージ管理ツールで、PHPアプリケーションの依存関係を管理します。
- 最後に、作業ディレクトリを /var/www に設定します。これは、コンテナ内の作業ディレクトリを指定するもので、PHPアプリケーションのコードを配置する場所として使用されます。

### php/php.ini
```php.ini
[PHP]
post_max_size = 100M
upload_max_filesize = 100M
variables_order = EGPCS

```

**php.iniはPHPの設定ファイルです。**

- post_max_size = 100M: HTTP POST リクエストで送信できるデータの最大サイズを設定します。ここでは、100MBまでのデータを受け付けるように設定されています。

- upload_max_filesize = 100M: 単一のアップロードファイルの最大許容サイズを設定します。つまり、アップロードされる個々のファイルが100MBを超える場合、アップロードは失敗します。

- variables_order = EGPCS: PHPでスーパーグローバル変数 （＄_GET、＄_POST、＄_COOKIE、＄_FILES、＄_SERVER）を取得する際の優先順位を指定します。ここでは、EGPCS は ＄_ENV、＄_GET、＄_POST、＄_COOKIE、＄_SERVER の順に取得するように指定されています。

###  src/index.php
```index.php
<?php
phpinfo();

```
[http://localhost:8000/](http://localhost:8000/) にアクセスしたときに表示する検証用ファイル。
不要になった場合は削除して大丈夫です。
### docker-compose.yml
```docker-compose.yml
version: '3'
services:
  nginx:
    image: nginx:1.25.0
    ports:
      - 8000:80
    volumes:
      # ./srcフォルダをコンテナ内の/var/www/htmlにマウント
      - ./src:/var/www/html
      # ./docker/nginxフォルダをコンテナ内の/etc/nginx/conf.dにマウント
      - ./docker/nginx:/etc/nginx/conf.d
    # 依存関係を設定
    depends_on:
      - app
  # PHP-FPMの定義
  app:
    build:
      # Dockerfileを格納するフォルダのパス
      context: ./docker/php
      # Dockerfileのファイル名
      dockerfile: Dockerfile
    # コンテナ内で使用される環境変数を定義(SQLへのアクセス情報)
    environment:
      MYSQL_ROOT_PASSWORD: 
      MYSQL_DATABASE: 
      MYSQL_USER: 
      MYSQL_PASSWORD: 
    volumes:
      # ./srcフォルダをコンテナ内の/var/www/htmlにマウント
     - ./src:/var/www/html   
     # 依存関係を設定(mysqlの起動後に起動する設定)
    depends_on:
      - mysql
    # MySQLの定義
  mysql:
    # MySQL コンテナに使用するイメージを指定
    image: mysql:8.0
    # コンテナ内で使用される環境変数を定義
    environment:
      MYSQL_ROOT_PASSWORD: 
      MYSQL_DATABASE: 
      MYSQL_USER: 
      MYSQL_PASSWORD: 
    volumes:
      # 名前付きボリュームを MySQL コンテナに紐づける
      - mysqldata:/var/lib/mysql
      - ./docker/mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      # - ./docker/mysql/sql:/docker-entrypoint-initdb.d
    ports:
      - 3306:3306
volumes:
  # 名前付きボリュームの作成
  mysqldata:

```
**docker-compose.ymlはdocker でコンテナをビルドする際に必要な設定を記したファイルです。**
- Nginxサービス: Nginxのコンテナを作成し、バージョン1.25.0の公式Nginxイメージを使用します。ホストのポート8000をコンテナのポート80にマッピングし、ホストの ./src ディレクトリをコンテナの /var/www/html にマウントします。さらに、ホストの ./docker/nginx ディレクトリをコンテナの /etc/nginx/conf.d にマウントします。このサービスは app サービスに依存しています。

- appサービス (PHP-FPM): Dockerfileを使ってPHP-FPMコンテナをビルドします。ビルドには、./docker/php ディレクトリにある Dockerfile を使用します。このサービスはMySQLサービス (mysql) に依存しています。

依存関係にあるコンテナは依存先のコンテナがビルド完了後にビルドされます。

- mysqlサービス: MySQLの公式イメージを使用してMySQLコンテナを起動します。データベースの永続化のために、ボリューム mysqldata を使用します。MySQLに関連する環境変数を設定し、ホストの ./docker/mysql/my.cnf をコンテナの /etc/mysql/conf.d/my.cnf にマウントします。MySQLのポート3306をホストのポート3306にマッピングしています。

詳しい解説は参考資料にて確認お願いします。
参考資料
[Docker で PHP + PHP-FPM × Nginx × MySQL の開発環境を構築](https://qiita.com/shikuno_dev/items/f236c8280bb745dd6fb4)
## 7. エラー時の対処法

`docker compose ps` でコンテナが起動できているか確認する
できていれば以下のように出力されるはずです。
```Terminal
$ docker compose ps
NAME           IMAGE          COMMAND                  SERVICE   CREATED         STATUS         PORTS
test-app-1     test-app       "docker-php-entrypoi…"   app       4 seconds ago   Up 2 seconds   9000/tcp
test-mysql-1   mysql:8.0      "docker-entrypoint.s…"   mysql     4 seconds ago   Up 3 seconds   0.0.0.0:3306->3306/tcp, 33060/tcp
test-nginx-1   nginx:1.25.0   "/docker-entrypoint.…"   nginx     4 seconds ago   Up 2 seconds   0.0.0.0:8000->80/tcp
```

起動していない場合は、`docker compose logs`コマンドで確認して見てください。
ポートが既に使われていないか確認してください。
<br>
バージョンを変更し互換性がなくなっている可能性もあります。