DockerでPHP（Laravel）+ nginx + MySQLのLEMP環境を構築する

## 概要
Docker, Docker Compose を使って、`PHP（Laravel） + nginx + MySQL`の`LEMP環境`を構築する記事です。

検索するとDockerfileやdocker-compose.ymlの書き方については色んな方が記事を書いてくださっていて、動く環境を作ること自体は難しくありませんでした。

ですが、筆者はひとつコンテナを作っては動作確認していくという過程を経てすごく理解が深まったなと思うので、ファイルの書き方だけでなく動作確認したことやその結果を含めて記事に残しておきたいと思います。

↓　完成後のリポジトリはこちらです。

https://github.com/hinakonagao/laravel_docker_sample


# 全体像

最終的なディレクトリ構成は以下の通りです。

全コンテナを管理するdocker-compose.ymlがトップレベルにあり、同じくトップレベルにあるdockerディレクトリ配下に各コンテナのDockerfileや設定ファイルを置いています。

```:tree
docker_sample/
├── src    // Laravelプロジェクトのソースコード
├── docker
│   ├── app    // PHPコンテナ
│   │   ├── Dockerfile
│   │   └── php.ini
│   ├── db     // MySQLコンテナ
│   │   ├── Dockerfile
│   │   └── my.conf
│   └── web    // nginxコンテナ
│       ├── Dockerfile
│       └── default.conf
└──  docker-compose.yml    // 全コンテナの管理
```

早速一つずつコンテナを作っていきます。

# PHPのコンテナ（Laravelの開発環境）

まずはLaravelの開発環境を構築するための、PHPのコンテナを作成します。

- docker-compose.ymlへの記述
- PHPコンテナ用のDockerfile
- PHPコンテナ用の設定ファイル（php.ini）

について順に説明します。

## docker-compose.yml（PHP）

docker-compose.ymlのうち、PHPコンテナについての記述は以下の通りです。
※docker-compose.ymlはインデントが意味を持つので注意。

```php:docker-compose.yml
version: "3.9"

services:
  app:    // サービス名
    build:
      context: .
      dockerfile: ./docker/app/Dockerfile
    volumes:
      - ./src/:/app
```

- `version:`
    
    これはPHPコンテナについてではなく `docker-compose.yml` の先頭に書く設定ですが、Composeファイルのバージョンを表しています。
    今回は最新の `3.9` を使います。（最新のバージョンは下記の公式サイトを参照）
    [Compose file](https://docs.docker.com/compose/compose-file/)
    
- `build:`
    
    ビルドコンテキストを指定します。
    - context：ビルドコンテキスト（buildを実行する場所）の設定
    - dockerfile：buildするDockerfileまでのパス（docker-compose.ymlから見た相対パス）
    
    参考記事： [docker-compose.ymlのbuild設定はとりあえずcontextもdockerfileも埋めとけって話 - Qiita](https://qiita.com/sam8helloworld/items/e7fffa9afc82aea68a7a)
    
- `volumes:`
    
    ホスト側の `./src/`をコンテナ側の `/app` にマウントするという意味になります。
    
    ※ここで `services:` の中に書いているこの `volumes:` は `バインドマウント` を行っています。
    
    参考記事：[Dockerのマウント3種類についてわかったことをまとめる - Qiita](https://qiita.com/y518gaku/items/456f34c317a65a9dae86)
    
    何が起きているかは実際の挙動を見てみた方が分かりやすいと思うので、後ほどまた動作確認します。

## Dockerfile（PHP）

Dockerfileはテキストファイルであり、Dockerイメージを作り上げるために実行する命令をこのファイルに書きます。

まず作成するDockerfileの全文がこちらです。

```php:/docker/app/Dockerfile
FROM php:8.0-fpm

ENV TZ Asia/Tokyo

RUN apt-get update && \
	apt-get install -y git unzip libzip-dev libicu-dev libonig-dev && \
	docker-php-ext-install intl pdo_mysql zip bcmath
		
COPY ./docker/app/php.ini /usr/local/etc/php/php.ini

COPY --from=composer:2.0 /usr/bin/composer /usr/bin/composer

WORKDIR /app
```
<br>
一つずつ説明します。

```php
FROM php:8.0-fpm
```

 `FROM`ではイメージをビルドするためのベースイメージを設定します。
書き方は `FROM イメージ名:タグ名` です。
ここではDocker HubからPHP公式のイメージをベースとして指定しています。
[Php - Official Image | Docker Hub](https://hub.docker.com/_/php)
<br>

```php
ENV TZ Asia/Tokyo
```

 `ENV`はコンテナ内のサーバー環境変数を設定します。
<br>


```php
RUN apt-get update && \
	apt-get install -y git unzip libzip-dev libicu-dev libonig-dev && \
	docker-php-ext-install intl pdo_mysql zip bcmath
```

 `RUN` にはコンテナビルド時に実行するコマンドを書きます。
 `&&` で複数のコマンドをつなぎ、 `\` で改行します。
※ `&&` と `\` を使うことで複数コマンドを1レイヤーにまとめることができ、公式でもベストプラクティスのTipsとして挙げられています。
[Intro Guide to Dockerfile Best Practices - Docker Blog](https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/#:~:text=Tip%20%233%3A%20Identify%20cacheable%20units%20such%20as%20apt%2Dget%20update%20%26%20install)

`apt-get update` ：インストール可能なパッケージの「一覧」を更新
 `apt-get -y install`：パッケージをインストール
 `docker-php-ext-install`：PHPの拡張ライブラリをインストール
※Laravelのインストールや開発に必要な（もしくは便利な）パッケージや拡張ライブラリをインストールしています。
※試しにこれらを全くインストールせず手順を進めてみると、コンテナを起動することはできますが、その後のLaravelプロジェクトを作成する時に大量のエラーに遭遇しました（笑）
<br>


```php
COPY ./docker/app/php.ini /usr/local/etc/php/php.ini
```

ローカルで（後ほど）作成する `php.ini` （PHPの設定ファイル）をDockerコンテナ内にコピーします。
書き方は `COPY [ローカル側のパス] [コンテナ側のパス]`です。

※ローカル側のパスは、Dockerfileから見てではなくbuildコマンドを実行するディレクトリから見た相対パスです。今回はDocker composeを使ってイメージビルドを行うので、docker-compose.ymlから見た相対パスになっています。
<br>


```php
COPY --from=composer:2.0 /usr/bin/composer /usr/bin/composer
```

Laravelを使うためComposerをインストールします。
この書き方により**マルチステージビルド**という方法でインストールされます。
※マルチステージビルドという方法はイメージの軽量化に役立つようです。
公式ドキュメント：[Use multi-stage builds](https://docs.docker.com/develop/develop-images/multistage-build/)
<br>

```php
WORKDIR /app
```

コンテナを起動している時に `$ docker-compose exec コンテナ名 bash` というコマンドを実行すると、コンテナの中でbashを実行することができるのですが、 `WORKDIR`はその時のカレントディレクトリを指定しています。


## 設定ファイル（php.ini）

 `COPY ./docker/app/php.ini /usr/local/etc/php/php.ini` で出てきたPHPの設定ファイル `php.ini`を作成します。

作成したファイルの中身がこちらです。

```:/docker//app/php.ini
zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /var/log/php/php-error.log
default_charset = UTF-8

[Date]
date.timezone = Asia/Tokyo

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese
```

こちらの記事の開発用php.iniをまるっとお借りしました。
開発用 / 本番用の設定例と、項目ごとの説明も載っていて面白かったので、ぜひご覧ください。
参考記事：[PHP7.4 ぼくのかんがえたさいきょうのphp.ini - Qiita](https://qiita.com/ucan-lab/items/0d74378e1b9ba81699a9)

## PHPコンテナを起動する

ここまででPHPのコンテナの準備が出来たので、実際に起動してみます。

docker-sompose.ymlのあるディレクトリで以下のコマンドを実行します。

```
$ docker-compose up -d --build
```

- `docker compose up` は `docker-compose.yml` に定義したサービスを起動します。
- `-d` 「デタッチド」モードでコンテナを起動します。
    （デフォルトは「アタッチド」モードで全てのコンテナログを画面上に表示する。「デタッチド」モードではバックグラウンドで動作する。）
    
- `--build` コンテナの開始前にイメージをビルドします。
    （特に変更がない場合はキャッシュが使用される。）
    

※ `$ docker-compose build` → `$ docker-compose up -d` を順に行うのと同じです。

### 起動したコンテナを確認する

以下のコマンドで起動中のコンテナを一覧で確認することができます。

```
$ docker-compose ps

NAME                  COMMAND                  SERVICE             STATUS              PORTS
docker_sample-app-1   "docker-php-entrypoi…"   app                 running             9000/tcp
```

STATUSがrunningになっていれば正常に起動しています。

今はPHPのコンテナしか作っていないので1つだけ表示されていますが、この後nginx, MySQLのコンテナを作り、最終的に3つのコンテナが表示されるようにします。

ここで今作業しているディレクトリの構成を確認してみます。

```
$ tree
.
├── src    // 作成された！
├── docker
│   ├── app
│   │   ├── Dockerfile
│   │   └── php.ini
└── docker-compose.yml
```

するとdocker-compose.ymlに書いたバインドマウントの以下の部分を受けて、コンテナを起動したときに自動でsrcディレクトリが作成されたことが確認できます。（同様にコンテナ内にもappディレクトリが作成されています。）

```php
volumes:
  - ./src/:/app
```



### コンテナの中に入ってみる

コンテナを起動中に以下のコマンドを実行すると、コンテナの中に入ってbashを実行することができます。

```
$ docker-compose exec app bash
// appの部分はサービス名を指定する
```
これで今コンテナの中に入れたので、Dockerfileの記述通りにコンテナが作られているか、またPHP・Composer・インストールした拡張機能が使えるか確認していきます。

```
// Dockerfileの「WORKDIR /app」で指定したカレントディレクトリ通りか確認
[app]:/app$ pwd
/app

// PHPのバージョン確認
[app]:/app$ php -v
PHP 8.0.15 (cli) (built: Jan 26 2022 17:38:36) ( NTS )
Copyright (c) The PHP Group
Zend Engine v4.0.15, Copyright (c) Zend Technologies

// Composerのバージョン確認
[app]:/app$ composer -v
   ______
  / ____/___  ____ ___  ____  ____  ________  _____
 / /   / __ \/ __ `__ \/ __ \/ __ \/ ___/ _ \/ ___/
/ /___/ /_/ / / / / / / /_/ / /_/ (__  )  __/ /
\____/\____/_/ /_/ /_/ .___/\____/____/\___/_/
                    /_/
Composer version 2.0.14 2021-05-21 17:03:37

// gitのバージョン確認
[app]:/app$ git --version
git version 2.30.2

// インストール済の拡張機能の一覧
[app]:/app$ php -m
[PHP Modules]
bcmath
intl
pdo_mysql
zip
// たくさん出てくるので他は省略

// php.iniがコピー出来ているか確認
[app]:/app$ cat /usr/local/etc/php/php.ini
zend.exception_ignore_args = off
expose_php = on
max_execution_time = 30
max_input_vars = 1000
upload_max_filesize = 64M
post_max_size = 128M
memory_limit = 256M
error_reporting = E_ALL
display_errors = on
display_startup_errors = on
log_errors = on
error_log = /var/log/php/php-error.log
default_charset = UTF-8

[Date]
date.timezone = Asia/Tokyo

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japaneseroot@0e3ba825df88
```

確認できたのでコンテナを抜けます。

```
$ exit

// もしくは ctrl + d
```

### コンテナの外からコマンドを実行する

上記のコンテナの中で実行したコマンドは、 `$ docker-compose exec サービス名 実行したいコマンド` でコンテナの外から実行することもできます。（コンテナが起動中に限る）

### バインドマウントの挙動を確認する

コンテナの外からコマンドが実行できるという確認も兼ねて、バインドマウントの動きを見る為、試しに以下のコマンドを実行してみます。

```
// コンテナ内の/app配下にファイルを作ってみる
$ docker-compose exec app touch sample.php
$ docker-compose exec app pwd
/app
$ docker-compose exec app ls
sample.php
```

コンテナ内の/app配下にファイルを作成しました。

この/appという場所は、docker-compose.ymlに書いた以下の記述の通りにバインドマウントされています。

```php
volumes:
  - ./src/:/app
```

これにより、プロジェクトディレクトリ配下の/srcにもsample.phpが作成されています。

```
$ ls src
sample.php
```

以上より、コンテナ内の `/app` に対して行ったことが、プロジェクトの `/src` に反映していることが分かりました。

<br>
では次に `/src` への変更がコンテナ内の `/app` に対しても反映するか試してみます。

 `/src/sample.php` をエディタで開いてファイルに `hello` と書きこみ、以下のコマンドを実行します。

```
$ docker-compose exec app cat sample.php
hello

// この時permission errorが出たら権限を変更する
$ sudo chmod -R 777 ./src
```

するとコンテナ内の `./app/sample.php` も編集されていることがわかります。

このようにバインドマウントにより、ホスト側のディレクトリがコンテナ内へマウント出来ていることも確認できました。

試しに作成したファイルは不要なので消しておきます。

```
$ rm src/sample.php
```


# nginxのコンテナ（webサーバー）
webサーバーとなるnginxのコンテナを作成します。

PHPコンテナのときと同じく、

- docker-compose.ymlへの記述
- nginxコンテナ用のDockerfile
- nginxコンテナ用の設定ファイル（default.conf）

の流れで説明します。

## docker-compose.yml（nginx）

既に作成しているdocker-compose.ymlに、nginxのコンテナについての部分を追記します。

```php:docker-compose.yml
services:
  api:

// 中略
	
  web:    // サービス名
    build:
      context: .
      dockerfile: ./docker/web/Dockerfile
    ports:
      - 8081:80
    depends_on:
      - app
    volumes:
      - ./src/:/app
```

解説していきます。

- `build:` ・`volumes:`
    
    ここはPHPコンテナと同様なので説明は省きます。
    
- `ports:`
    
    ホスト側とコンテナ間のポート番号の対応付けを設定します。
    書き方は `ホスト側のポート番号 : コンテナのポート番号`です。
    
    ※今回ホスト側（自分のPC）は既に他の開発で使っているポートとの兼ね合いで `8081` を使いました。コンテナ側はnginxのデフォルトのポート番号である `80` にしています。
    
- `depends_on:`
    
    サービスの起動順序を制御します。
    
     `web` の `depends_on` に `app` と書いているので、 `app` → `web` の順に起動するように指定しています。
    
    ※但しこの記述なしで `$ docker-compose up -d --build` をしてみても私の環境では全く問題なく動きました。が、サービス同士の依存関係を明示的に記すという意味でも書いておくに越したことはないという判断で書いています。
    
    ※ nginxとphp間でTCPによるfpm接続についてや、`depends_on` オプションについては以下の記事がすごく勉強になったので是非読んでみてください。
    参考記事：[【docker-compose】depends_onとサービス名解決にまつわるエトセトラ - Qiita](https://qiita.com/haruyan_hopemucci/items/344f1e2fb95ed452bdb2#%E5%90%84%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%8C%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E5%90%8D%E3%81%AE%E5%90%8D%E5%89%8D%E8%A7%A3%E6%B1%BA%E3%82%92%E5%BF%85%E8%A6%81%E3%81%A8%E3%81%99%E3%82%8B%E3%82%BF%E3%82%A4%E3%83%9F%E3%83%B3%E3%82%B0)

## Dockerfile（nginx）

作成するDockerfileの全文はこちらです。

```php:/docker/web/Dockerfile
FROM nginx:1.20-alpine

ENV TZ Asia/Tokyo

COPY ./docker/web/default.conf /etc/nginx/conf.d/default.conf
```

 `FROM` , `ENV` , `COPY` の意味はPHPコンテナのDockerfileと同様なので詳しい説明は省略し、このファイルで設定していることを簡単にまとめます。

- nginx公式のイメージ（Alpineベース）をベースイメージに使用
[Nginx - Official Image | Docker Hub](https://hub.docker.com/_/nginx)
参考記事：[Dockerでよく利用されているAlpineは他のLinuxディストリビューションと比べて、どれだけ軽量なのか - プログラミングは芸術だ！](https://charlie1012.hatenablog.jp/entry/2021/01/14/090000)

- 環境変数のタイムゾーンを設定する
- nginxの設定ファイル（default.conf）をコンテナ内にバインドマウント

※ちなみにnginxは1.18, 1.20などの偶数バージョンが安定バージョンであり、安定バージョンの使用を推奨されています。
公式ドキュメント：[Installing NGINX Open Source](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#stable_vs_mainline)

## 設定ファイル（default.conf）

`./docker/web/default.conf`を作成します。
Laravel公式に用意されているnginxの設定例をべースに使います。
[Laravel 8.x デプロイ](https://readouble.com/laravel/8.x/ja/deployment.html)

 `root` と`fastcgi_pass`の設定のみ、このプロジェクトに合わせて書き換えています。

```:/docker/web/default.conf
server {
    listen 80;
    server_name example.com;
    root /app/public;    // 書き換え

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.php;

    charset utf-8;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_pass app:9000;    // 書き換え
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }
}
```

- `root`

リクエストのルートディレクトリです。
`root /app/public;` と書いたので、 `localhost:8081`（docker-compose.ymlで設定したポート番号）にアクセスすると `/app/public`を見に行きます。

- `fastcgi_pass`

FastCGIサーバーのアドレスです。
※FastCGI：Webサーバ上で動くプログラムを一度起動したらしばらく待機させることによって、プログラムの開始と終了にかかる手間を減らし、動きを速くしたりWebサーバの負荷を軽減することができる仕組み。
`app:9000;` と書いたので、appコンテナの9000番ポートを指定しています。


## nginxコンテナを起動する

nginxのコンテナの準備が出来たので、また実際に起動してみます。

docker-sompose.ymlのあるディレクトリで以下のコマンドを実行します。

```
$ docker-compose up -d --build

$ docker-compose ps
NAME                  COMMAND                  SERVICE             STATUS              PORTS
docker_sample-app-1   "docker-php-entrypoi…"   app                 running             9000/tcp
docker_sample-web-1   "/docker-entrypoint.…"   web                 running             0.0.0.0:8081->80/tcp
```

api（PHPのコンテナ）、web（nginxのコンテナ）の2つが起動できました。

またdocker_sample-web-1の `PORTS` が `0.0.0.0:8081->80/tcp` となっており、ホスト上の8081番ポートをコンテナの80番ポートへ割り当てられていることも確認できます。

### コンテナの動作確認

nginxのバージョンを確認します。

```
$ docker-compose exec web nginx -v
nginx version: nginx/1.20.2
```

コンテナの中に入ってみます。

```php
$ docker-compose exec web bash
OCI runtime exec failed: exec failed: container_linux.go:380: starting container process caused: exec: "bash": executable file not found in $PATH: unknown
```

するとエラーになりました。
Alpineをベースとすると `bash` は使えず、 `ash` や `sh` は使えるようです。

```php
$ docker-compose exec web ash
[web]:/ $ pwd
/
```

無事コンテナの中に入れました。

### webサーバーとしての動作確認

- リクエストに対してファイルを返しブラウザで表示できる
- nginxのコンテナからPHPのコンテナへphpを実行させることができる

上記2点を確認します。

```
$ mkdir src/public
$ touch src/public/test.php
```

test.phpを以下のように編集します。

```php:/src/public/test.php
<?php 

echo 'test.phpです';
phpinfo();
```

`http://localhost:8081/test.php` にアクセスすると以下のように表示され、webサーバーが正しく動作していることが確認できます。
![スクリーンショット 2022-01-30 151523.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1208065/09b64c9d-b5e1-4b36-e0e5-0ff43c9e5dcb.jpeg)

試しに作成したファイルは不要なので消しておきます。

```
$ rm -rf src/*
```

# MySQLのコンテナ（データベース）

データベースのMySQLコンテナを作成します。

以下の流れで説明します。

- docker-compose.ymlへの記述
- MySQLコンテナ用のDockerfile
- MySQLコンテナ用の設定ファイル（my.conf）

## docker-compose.yml（MySQL）

既に作成しているdocker-compose.ymlに、MySQLのコンテナについての部分を追記します。

```php:docker-compose.yml
services:
	
// 中略

  db:    // サービス名
    build:
      context: .
      dockerfile: ./docker/db/Dockerfile
    ports:
      - 3306:3306
    environment:
      MYSQL_DATABASE: database
      MYSQL_USER: user
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password
      TZ: 'Asia/Tokyo'
    volumes:
      - mysql-volume:/var/lib/mysql
      
volumes:
  mysql-volume:
```

- `build:` ・ `ports:`
    
    既出の通りです。
    
- `environment:`
    
    環境変数の設定です。
    ※名前やDB名・ユーザー名・パスワードは好きなものを設定します。
    ※実際のプロジェクト管理では、環境変数は `.env` に書いて `.gitignore` にするなどして、重要な情報が公開されないようにします。
    
    - MYSQL_DATABASE：DB名
    - MYSQL_USER：ユーザー名
    - MYSQL_PASSWORD：パスワード
    - MYSQL_ROOT_PASSWORD：ルート権限のパスワード
    - TZ：時間設定（Time Zone）
    
- `volumes:`
    
    考え方はPHPやnginxのコンテナ同様ですが、先ほどまではホスト側のディレクトリを書いていたところに `mysql-volume` と書いています。
    これにより `mysql-colume` という名前で作成した**名前付きボリューム**とコンテナ内を紐づけています。

### Docker Volume

 `Volume`とは、**コンテナを破棄してもデータを永続的に保存できる**ように、コンテナ外に提供されているデータの保存領域です。
Dockerの管理下でホスト上にストレージ領域を確保しており、Linux なら `/var/lib/docker/volumes/`以下にあります。

参考記事：[Docker、ボリューム(Volume)について真面目に調べた - Qiita](https://qiita.com/gounx2/items/23b0dc8b8b95cc629f32)

 **なぜVolumeが必要なのか？**
コンテナが起動している間はDBのレコードは保存された状態が続きますが、例えば以下のようなコマンドでコンテナを新しく作り直したとするとデータベースの情報はゼロに戻ってしまいます。

```
// コンテナ削除
$ docker-compose down

// コンテナ起動
$ docker-compose up -d
```

これでは困るのでコンテナを破棄してもデータを残したい、、、というときにVolumesを使ってデータの永続化を行います。

※ボリュームには名前付きボリュームと匿名ボリュームがありますが、通常は管理しやすい名前付きボリュームを使うと良いかと思います。

名前付きボリュームを作成する場合は、データの永続化対象のコンテナに対して `volumes:` オプションでバインドマウントを行うだけでなく、以下のようにdocker-compose.ymlのトップレベルでボリューム名を定義します。

```php:docker-compose.yml
volumes:
  - mysql-volume:
```

## Dockerfile（MySQL）

作成するDockerfileの全文はこちらです。

```php:/docker/db/Dockerfile
FROM mysql:8.0

COPY ./docker/db/my.conf /etc/my.conf
```

内容を簡単にまとめると、

- 公式のMySQLイメージをベースイメージに使用
[Mysql - Official Image | Docker Hub](https://hub.docker.com/_/mysql)
- MySQLの設定ファイル（my.conf）をコンテナ内にバインドマウント

※ここで使用しているイメージではM1 Macでは動作しないという情報が見られました。どうやらOracleのMySQLチームがメンテしている `mysql/mysql-server` のイメージだと動作するようです。
[mysql-server  | Docker Hub](https://hub.docker.com/r/mysql/mysql-server)

## 設定ファイル（my.conf）

`./docker/db/my.conf`を作成します。

色んな記事で作成されているmy.confを参考にさせていただきながら書きました。
参考記事：[【Docker】docker-composeでmysqlのコンテナを立てる](https://it-blue-collar-dairy.com/mysql_on_docker-compose/)

- 文字コード
- タイムゾーン
- ログ

の設定を行っています。

```:/docker/db/my.conf
[mysqld]
# character
character_set_server = utf8mb4
collation_server = utf8mb4_0900_ai_ci

# timezone
default-time-zone = SYSTEM
log_timestamps = SYSTEM

# Error Log
log-error = mysql-error.log

# Slow Query Log
slow_query_log = 1
slow_query_log_file = mysql-slow.log
long_query_time = 1.0
log_queries_not_using_indexes = 0

# General Log
general_log = 1
general_log_file = mysql-general.log

[mysql]
default-character-set = utf8mb4

[client]
default-character-set = utf8mb4
```

## MySQLコンテナを起動する

MySQLのコンテナの準備が出来たので、また実際に起動してみます。

docker-sompose.ymlのあるディレクトリで以下のコマンドを実行します。

```
$ docker-compose up -d --build

$ docker-compose ps
NAME                  COMMAND                  SERVICE             STATUS              PORTS
docker_sample-app-1   "docker-php-entrypoi…"   app                 running             9000/tcp
docker_sample-db-1    "docker-entrypoint.s…"   db                  running             0.0.0.0:3306->3306/tcp
docker_sample-web-1   "/docker-entrypoint.…"   web                 running             0.0.0.0:8081->80/tcp
```

api（PHPのコンテナ）、web（nginxのコンテナ）、db（MySQLのコンテナ）の3つが起動できました。

### コンテナの動作確認

MySQLのバージョンを確認します。

```
$ docker compose exec db mysql -V
mysql  Ver 8.0.28 for Linux on x86_64 (MySQL Community Server - GPL)
```

※この後Laravelのプロジェクトを作成したら、このDBとLaravelを接続します。
　そしてマイグレーションを行ってDBにテーブルを作成してから、MySQLにログインしてDBを使う動作確認をしたいと思います。

# Laravelのインストール

## Laravelプロジェクトの作成

LEMP環境が構築できたので、Laravelのアプリケーションを作っていきます。

appコンテナに入り、Laravelをインストールします。

```
$ docker compose exec app bash
[app]:/app$ composer create-project --prefer-dist "laravel/laravel=8.*" .
[app]:/app$ php artisan -v
Laravel Framework 8.81.0
```

コンテナ内の/app配下にLaravelのプロジェクトが新規作成され、ホスト側の/src配下にも同じくLaravelのプロジェクトが出来ました。

`localhost:8081`にアクセスして、ブラウザでもLaravelのウェルカムページが表示できることを確認します。
![スクリーンショット 2022-01-30 172811.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1208065/542d96bd-733c-4328-33f4-83246f8eacd7.jpeg)

## DB接続

appコンテナ（Laravel）からdbコンテナ（MySQL）へ接続する設定を行います。

Laravelではデータベースへの接続設定を `.env` ファイルに定義しているので、 `/src/.env` のDBの部分を以下のように修正します。

```bash:/src/.env
DB_CONNECTION=mysql
DB_HOST=db    // MySQLコンテナのサービス名
DB_PORT=3306
DB_DATABASE=database
DB_USERNAME=root
DB_PASSWORD=passwor
```

- `DB_HOST` にはMySQLコンテナのサービス名を指定します。
- その他の項目もMySQLコンテナで設定した値（今回はdocker-compose.ymlのenvironmentで定義）と同じ値を指定します。

DBに接続出来ているか確認する為、以下のコマンドを実行してマイグレーションを行います。

```
$ docker compose exec app bash
[app]:/app$ php artisan migrate
Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (55.42ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (51.11ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (45.12ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (75.74ms)
```

## MySQLを使ってみる

MySQLのコンテナに入ってDBを確認してみます。

```
$ docker-compose exec db bash
[db]:/$ mysql -u root -p
// パスワードを求められるので入力
[db] mysql> use database;
[db] mysql> show tables;
+------------------------+
| Tables_in_database     |
+------------------------+
| failed_jobs            |
| migrations             |
| password_resets        |
| personal_access_tokens |
| users                  |
+------------------------+
5 rows in set (0.00 sec)
```

先ほどマイグレーションを実行したので、Laravelのデフォルトで用意されているマイグレーションファイル通りにテーブルが作成されていることが確認できました。

## 最後に

これでDocker（Docker Compose）を使ったLEMP環境の構築が完了です。

今回作成した環境は非常にシンプルなものなので、業務で使うとなるともっと設定を細やかに行ったり、開発環境・ステージング環境・本番環境それぞれの設定ファイルを用意して設定を切り替えたりといった作業が必要になるかと思いますが、仕組みが分かればあとは全てこの延長にあるのかなと思います。

またいずれ環境ごとの設定ファイル切り替えだったり、あとはNodeのコンテナを使ってNext.jsの環境構築もやってみたいです。

## 参考記事

https://yutaro-blog.net/2021/04/29/docker-laravel-vuejs-2/

https://yutaro-blog.net/2021/04/30/docker-laravel-vuejs-3/#index_id11

https://qiita.com/ucan-lab/items/56c9dc3cf2e6762672f4

https://laraweb.net/environment/8652/

https://reffect.co.jp/laravel/finally-understand-laravel-on-docker