【超入門】20分でLaravel開発環境を爆速構築するDockerハンズオン

前置きが長いので、Docker, Git, GitHubの設定が終わってる人は[ここ](https://qiita.com/ucan-lab/items/56c9dc3cf2e6762672f4#%E5%88%9D%E5%BF%83%E8%80%85%E5%90%91%E3%81%9120%E5%88%86%E3%81%A7laravel%E9%96%8B%E7%99%BA%E7%92%B0%E5%A2%83%E3%82%92%E7%88%86%E9%80%9F%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8Bdocker%E3%83%8F%E3%83%B3%E3%82%BA%E3%82%AA%E3%83%B3)から始められます。

## 【実際にLaravel開発を始める方へ】

本記事はハンズオンということもあり、初歩的な内容かつ最小限の構成になっております。
実際に開発を始める場合はより実用的なDocker構成の記事を書いてますので、こちらの記事をご参考ください。

- [最強のLaravel開発環境をDockerを使って構築する](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4)
- https://github.com/ucan-lab/docker-laravel
  - こちらのテンプレートリポジトリはご自由にご利用ください

## 概要

DockerとDocker Composeを使ってLEMP環境(PHP/nginx/MySQL)を構築し、Laravelの新規プロジェクト作成、構築した環境を破棄してから環境の再構築までをハンズオン形式で行います。

## 動作環境

Windows(WSL2), Mac(M1 Mac含む), Linux環境で動作するように書いてます。
筆者の実行環境はM1じゃないMacです。

## 更新履歴

- 2022/02/23 Laravel9がリリースされたので、合わせて記事をリニューアルしました。
  - PHP8.1, Composer2.2, Laravel9
- 2020/09/09 Laravel8(当時はLaravel6が出た頃)がリリースされ、こちらの記事も内容が古くなったので全て書き直しました。
- 2021/04/25 記事内容をアップデートしました
  - PHP7.4 => 8.0, Composer1.x => 2.0
  - MySQLベースイメージの変更([mysql](https://hub.docker.com/_/mysql) => [mysql/mysql-server](https://hub.docker.com/r/mysql/mysql-server))
- 2021/04/29 docker-compose を docker compose へ変更
- 2021/06/06 [Chrome 91より`10080`ポートへのアクセスがブロックされる](https://chromestatus.com/feature/6510270304223232)ため`8080`へ変更
  - @mzmt さんの[コメント](https://qiita.com/ucan-lab/items/56c9dc3cf2e6762672f4#comment-606b118c76aa45cfbbc1)より

## 目的

Dockerの細かい概念などはすっ飛ばして、
このハンズオンの通りに進めればDocker×Laravelの環境を構築できます。
習うより慣れろで行ってみましょう！

## ハンズオン終了時のGitHub

https://github.com/ucan-lab/docker-laravel-handson

## ハンズオンイベント

### 【ぺちオブ】ゆーきゃんプレゼンツ！Laravel + Docker 環境構築ハンズオン

https://phper-oop.connpass.com/event/137152

あの有名なぺちオブのイベントとしてハンズオン講師をやらせていただきました！
ありがとうございます🙏

### DockerでLaravelのローカル環境を構築するハンズオン(オンライン)

https://php-builders.connpass.com/event/211985

2021/05/01 第二回のハンズオンイベントを開催します。

### 【初心者向け】DockerでLaravel9のローカル環境を構築するハンズオン(オンライン)

https://php-builders.connpass.com/event/239066/

2022/02/23 第三回のハンズオンイベントを開催します。

## レビュワー

- hiroさん @hirodragon
- かひろくん @kahirokunn
- もっぷ先生 @kobayashi-m42
- こはさん @smspp

スペシャルサンクス🙇‍♂️🙏

## 前提条件

- Mac基準ですが、Windowsでも動作することを確認済み
- コマンドは `[環境] $` とprefixを付けてます。
- 所要時間は事前準備部分を除いて20分を目安としてます。
- エディタはファイル編集時にお好みのもので開いて作業ください🙏

## 構築環境、使用技術

- php: 8.1-fpm-buster
  - ピーエッチピー、プログラミング言語
  - 公式: https://www.php.net
  - PHPとは: https://www.php.net/manual/ja/intro-whatis.php
  - Dockerイメージ: https://hub.docker.com/_/php
- composer: 2.2
  - コンポサー、PHPのパッケージ管理ツール
  - 公式: https://getcomposer.org
  - Composerとは: https://getcomposer.org/doc/00-intro.md
  - Dockerイメージ: https://hub.docker.com/_/composer
- nginx: 1.20-alpine
  - エンジンエックス、ウェブサーバー
  - 公式: https://www.nginx.com
  - nginxとは: https://nginx.org/en
  - Dockerイメージ: https://hub.docker.com/_/nginx
- mysql/mysql-server: 8.0
  - マイエスキューエル、データベースサーバー
  - 公式: https://www.mysql.com/jp
  - MySQLとは: https://ja.wikipedia.org/wiki/MySQL
  - Dockerイメージ: https://hub.docker.com/r/mysql/mysql-server
- Laravel: 9.x
  - ララベル、PHPのフレームワーク
  - 公式: https://laravel.com
  - Laravelとは: https://readouble.com/laravel/9.x/ja/installation.html#why-laravel
  - Laravel本体: https://github.com/laravel/framework

それぞれ、どういった技術なのか抑えておくと理解がしやすいです。

## dockerおすすめ書籍

冒頭で習うより慣れろと言いましたが、習っておくのもそれはそれで良いことなのでオススメの書籍や記事をご紹介します。私はこの辺りの記事や本を読んで勉強したのでオススメしてます。

### マンガでわかるdockerシリーズ

- [#マンガでわかるDocker ① 〜概念・基本コマンド編〜](https://llminatoll.booth.pm/items/825879)
- [#マンガでわかるDocker ② 〜開発環境を作ろう編〜](https://llminatoll.booth.pm/items/1036317)
- [#マンガでわかるDocker ③ 〜AWS編〜](https://llminatoll.booth.pm/items/1242542)

@llminatoll さんがDockerについてマンガで分かりやすく説明されてます。 
初めての方はこちらのマンガでDockerの概念を学んでから進めると理解しやすいです。

## dockerおすすめ記事・サイト

### 公式系

- [Docker](https://www.docker.com)
- [Docker Hub](https://hub.docker.com)
- [Docker ドキュメント日本語化プロジェクト](http://docs.docker.jp/v1.9)
  - [用語集](http://docs.docker.jp/v1.9/engine/reference/glossary.html)

### 記事

- [【図解】Dockerの全体像を理解する -前編-](https://qiita.com/kotaro-dr/items/b1024c7d200a75b992fc)
  - [【図解】Dockerの全体像を理解する -中編-](https://qiita.com/kotaro-dr/items/88ec3a0e2d80d7cdf87a)
  - [【図解】Dockerの全体像を理解する -後編-](https://qiita.com/kotaro-dr/items/40106f13d47bfcbc2572)
- [Docker入門（第一回）～Dockerとは何か、何が良いのか～](https://knowledge.sakura.ad.jp/13265)
  - [Docker入門（第二回）～Dockerセットアップ、コンテナ起動～](https://knowledge.sakura.ad.jp/13795/)
  - [Docker入門（第三回）～各種dockerコマンドとDockerイメージ作成について～](https://knowledge.sakura.ad.jp/14427)
  - [Docker入門（第四回）～Dockerfileについて～](https://knowledge.sakura.ad.jp/15253/)
  - [Docker入門（第五回）〜コンテナ間通信〜](https://knowledge.sakura.ad.jp/16082/)
  - [Docker入門（第六回）〜Docker Compose〜](https://knowledge.sakura.ad.jp/16862/)

### スライド

- [Docker入門-基礎編 いまから始めるDocker管理【2nd Edition】](https://www.slideshare.net/zembutsu/introduction-to-docker-management-and-operations-2nd)
- [Dockerfileを改善するためのBest Practice 2019年版](https://www.slideshare.net/zembutsu/dockerfile-bestpractices-19-and-advice)
- [Dockerfile を書くためのベストプラクティス解説編](https://www.slideshare.net/zembutsu/explaining-best-practices-for-writing-dockerfiles)

## 事前準備

### Dockerのインストール確認

Dockerが使える状態になっていればokです。

```shell
[mac] $ docker --version
Docker version 20.10.10, build b485636
```

- [docker version](http://docs.docker.jp/engine/reference/commandline/version.html)

#### 【Macユーザー向け】

- https://docs.docker.com/docker-for-mac/install
- [🐳【すぐ開発を始めたい人向け】DockerをMacにインストールする手順🐳](https://qiita.com/techpit-jp/items/d17e2585888daa8d4bff)

#### 【Windowsユーザー向け】

- https://docs.docker.com/docker-for-windows/install
- [🐳WindowsでDockerを始める手順🐳](https://qiita.com/techpit-jp/items/f4a1319a909dd508f372)
- [Git Bashって使ってる？Windowsで動く意外にすごい便利ツール](https://www.sejuku.net/blog/72673)
- [Git for Windowsのbashが便利な件](https://qiita.com/ikegam1/items/ee592c84e0d102f2c550)

Git Bash を起動した際は下記のコマンドを実行ください。

```shell
$ exec winpty bash
```

- [Git Bashのttyで怒られないように](https://qiita.com/amanoese/items/7b237e8703c3b4c7f001)

#### 【Windowsユーザー(WSL2)向け】

- [WSL 2 での Docker リモート コンテナーの概要](https://docs.microsoft.com/ja-jp/windows/wsl/tutorials/wsl-containers)

### GitHubアカウントの作成

- https://github.com

### Gitの初期設定

`user.email` と `user.name` が設定されていればok!
Gitの初期設定を行ってない場合、下記の記事を参考に初期設定をお願いします。

[Mac Git 初期設定](https://qiita.com/ucan-lab/items/aadbedcacbc2ac86a2b3)

最終的に下記のように表示されればokです。

```shell
$ git config --list | grep user
user.email=xxx@yyy.com
user.name=zzz
```

### GitHub SSH接続設定

```shell
$ ssh -T github.com
Warning: Permanently added 'github.com,52.69.186.44' (RSA) to the list of known hosts.
Hi ucan-lab! You've successfully authenticated, but GitHub does not provide shell access.
```

`successfully authenticated` の文字が出ればok！
Warningは気にしなくてok！

GitHub SSH接続設定を行ってない場合、下記の記事を参考に初期設定をお願いします。
もしくはハンズオンをHTTPSに置き換えて進めてください。

[Mac GitHub SSH接続設定](https://qiita.com/ucan-lab/items/e02f2d3a35f266631f24)

最終的にgit cloneやgit pushができれば良いのでHTTPSでもokです。

### Docker起動確認

インストールしたDockerを起動してください。
`running` 状態になっていればokです。

<img width="401" alt="スクリーンショット 2019-09-29 1.39.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/4c72323d-219f-179a-a726-668e400b84c4.png">

***

ここまでで事前準備終了です。

***

## 今回のハンズオンのゴール

- 3層アーキテクチャのコンテナの構築
  - ウェブサーバー(web)
     - nginxで静的コンテンツ配信サーバを構築
  - アプリケーションサーバー(app)
     - nginxを経由してPHPを動作させるアプリケーションサーバを構築
     - PHPパッケージ管理ツールComposerのインストール
  - データベースサーバー(db)
     - MySQLデータベースサーバーの構築
- Laravelをインストールしてwelcome画面の表示
- LaravelとMySQLを連携し、マイグレーションを実行
- GitHubにソースコードをアップロード
- Docker環境の破棄
- Docker環境をGitHubから再構築

## 🐳【初心者向け】20分でLaravel開発環境を爆速構築するDockerハンズオン🐳

- 所要時間: 20分目安(解説読みながらだと1時間半程度)

### 作業ディレクトリを作成

```shell
[mac] $ mkdir docker-laravel-handson
[mac] $ cd docker-laravel-handson
```

### 【補足】最終的なディレクトリ構成

```shell-session
.
├── README.md (この名前にするとGitHubで見た時にHTMLに変換して表示してくれる)
├── infra (*1)
│   ├── mysql (*1)
│   │   ├── Dockerfile
│   │   └── my.cnf (*1)
│   ├── nginx (*1)
│   │   └── default.conf (*1)
│   └── php (*1)
│       ├── Dockerfile (この名前にするとファイル名の指定を省略できる)
│       └── php.ini (*1)
├── docker-compose.yml (この名前にするとファイル名の指定を省略できる)
└── src (*1)
    └── Laravelをインストールするディレクトリ
```

このディレクトリ構成を目指します。
`(*1)` 任意の名前に変更してもokです。

## リモートリポジトリを作成

https://github.com/new

![GitHub Create remote repository](https://i.imgur.com/f1K2wSM.png)
![GitHub docker-laravel-handson](https://i.imgur.com/82OiIAD.png)

リポジトリ名 `docker-laravel-handson` のGitHubリモートリポジトリを作成する。

## 初回コミット

```shell
[mac] $ echo "# docker-laravel-handson" >> README.md
[mac] $ git init
[mac] $ git add README.md
[mac] $ git commit -m "first commit"
[mac] $ git branch -M main
```

### 【補足】master → main デフォルトブランチの変更

※ 2020/10/1 からデフォルトブランチがmasterからmainに変更されました。

- [The default branch for newly-created repositories is now main](https://github.blog/changelog/2020-10-01-the-default-branch-for-newly-created-repositories-is-now-main/)

### 【補足】git init 時のメッセージ

```
$ git init
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint: 
hint: 	git config --global init.defaultBranch <name>
hint: 
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint: 
hint: 	git branch -m <name>
```

下記の設定をしておくと次回以降は `main` ブランチがデフォルトブランチとなります。

```
$ git config --global init.defaultBranch main
```

## リモートリポジトリの登録

リモートリポジトリを登録します。
**リモートリポジトリ先は適宜置き換えてください。**

```shell
[mac] $ git remote add origin git@github.com:ucan-lab/docker-laravel-handson.git
```

リモートリポジトリ先が正しく設定されていることを確認してください。

```
[mac] $ git remote -v
origin  git@github.com:ucan-lab/docker-laravel-handson.git (fetch)
origin  git@github.com:ucan-lab/docker-laravel-handson.git (push)

# もしリモートリポジトリ先を間違えた場合は下記のコマンドから変更できます。
[mac] $ git remote set-url origin <リモートリポジトリ>
```

リモートリポジトリ先の名前を `origin` と付けられていること。
リモートリポジトリ先のURL等に間違いがないことを確認してください。
(このままコピペすると私のリポジトリになってしまいます)

## リモートリポジトリへプッシュ

GitHubへpushします。

```shell
[mac] $ git push -u origin main
```

- `-u` オプションは `--set-upstream` の省略
 - ローカルリポジトリの現在のブランチ(main)をリモートリポジトリ(origin)のmainにpush先を設定
 - 以降は `git push` だけで `git push origin main` と同義

GitHubのリモートリポジトリのmainブランチへpushされていることを確認します。

### 【補足】お好みのGUIエディタで開く

ymlファイルをCUIエディタで書いていくのは大変なのでお好みのGUIエディタ(今回はVSCode)で開きます。
`code` コマンドがインストールされていれば、簡単にプロジェクトをVSCodeで開けます。

```
[mac] $ code .
```

![ScreenShot 2020-10-15 2.34.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/501a153a-882d-0995-200a-3f1024fd2b0e.png)

また、 `control` + `shift` + `@` で統合ターミナルをVSCodeで開くことができます。
ファイルの編集もコマンドの実行もVSCodeだけで行えます。

![ScreenShot 2020-10-15 2.36.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/ca352346-5026-1882-fa22-3bd0087552ca.png)

### アプリケーションサーバ(app)コンテナを作る

PHPアプリケーションサーバコンテナを作成します。
公式のPHP-FPMイメージをベースイメージとしてカスタマイズします。

- [Docker Hub php](https://hub.docker.com/_/php)
  - [サポートしてるバージョン](https://github.com/docker-library/docs/blob/master/php/README.md#supported-tags-and-respective-dockerfile-links)
- [nginx と PHP-FPM の仕組みをちゃんと理解しながら PHP の実行環境を構築する](https://qiita.com/kotarella1110/items/634f6fafeb33ae0f51dc)

#### 【補足】ディレクトリ構成

```shell-session
.
├── infra
│   └── php
│       ├── Dockerfile
│       └── php.ini # PHPの設定ファイル
├── src # Laravelをインストールするディレクトリ
└── docker-compose.yml
```

#### 【補足】【重要】ファイル編集について

`touch` コマンドで空ファイルを作成してます。
作成後はお好みのエディタで編集ください。

タイプミス防止のため空ファイルを先に作成してます。

コマンドを実行する場所を間違えないように `cd` コマンドは使用しないように注意してください。
当ハンズオンはこの流れで進めていきます。

あれこれ試したいと思いますが、まずはハンズオンが上手くいってから色々試してみてください。

#### docker-compose.yml を作成する

```shell
[mac] $ touch docker-compose.yml
```

作成した `docker-compose.yml` を下記の通りに編集します。

```yaml:docker-compose.yml
version: "3.9"
services:
  app:
    build: ./infra/php
    volumes:
      - ./src:/data
```

- `docker-compose.yml` ファイルはインデント(半角スペース)が意味を持ちます。注意してコピペしてください。

#### docker-compose.yml: 設定値の補足

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

Docker Composeファイルのバージョンを指定しています。

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-versioning/

通常はメジャー番号とマイナー番号を両方指定します。
ちなみにマイナーバージョンを指定しない場合はデフォルト`0`が使用されます。

```yaml:docker-compose.yml
# 下記は同じ指定になる
version: "3"
version: "3.0"
```

```yaml:docker-compose.yml
services:
  app: # => サービス名は任意
    build: ./infra/php
    volumes:
      - ./src:/data
```

サービス名に `app` (アプリケーションサーバー)の名前を付けて定義しています。
サービス名は任意に決められます。

`build:` で指定しているのはビルドコンテキストを指定します。
[ビルドコンテキスト](https://matsuand.github.io/docs.docker.jp.onthefly/develop/develop-images/dockerfile_best-practices/#%E3%83%93%E3%83%AB%E3%83%89%E3%82%B3%E3%83%B3%E3%83%86%E3%82%AD%E3%82%B9%E3%83%88%E3%81%AE%E7%90%86%E8%A7%A3)とは、`docker build`を実行する際の現在の作業ディレクトリのことをビルドコンテキスト(build context)と呼びます。

`Dockerfile` が置かれている `./infra/php` ディレクトリをビルドコンテキストとして指定します。
Dockerビルドの際は `Dockerfile` のファイルを探すので、ファイル名の指定は不要です。

`volumes:` ではホスト側のディレクトリや名前付きボリュームをコンテナ側へマウントしたい時に指定します。
今回はホスト側の `./src` ディレクトリをappサービスのコンテナ内 `/data` へマウントしてます。

#### ./infra/php/Dockerfile を作成する

- Composerコマンドのインストール
- Laravelで必要なPHP拡張機能のインストール
  - bcmath, pdo_mysql が不足しているので追加インストール

```shell
[mac] $ mkdir -p infra/php
[mac] $ touch infra/php/Dockerfile
```

- `mkdir -p` 必要に応じて親ディレクトリも作成してくれるオプションです。

下記のコードを丸ごとコピーして Dockerfile へ貼り付けてください。

```dockerfile:infra/php/Dockerfile
FROM php:8.1-fpm-buster

ENV COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer

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

RUN apt-get update && \
  apt-get -y install --no-install-recommends git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath

COPY ./php.ini /usr/local/etc/php/php.ini

WORKDIR /data
```

- [PHP拡張ライブラリのインストール方法](https://hub.docker.com/_/php/#how-to-install-more-php-extensions)
- [PECL拡張ライブラリのインストール方法](https://hub.docker.com/_/php/#pecl-extensions)

#### 【補足】./infra/php/Dockerfile

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

各種Docker命令を解説します。

```
FROM php:8.1-fpm-buster
```

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#from

FROM命令はイメージビルドのためのベースイメージを設定します。
`FROM イメージ名:タグ名` で指定します。

[php | Docker Hub](https://hub.docker.com/_/php)公式イメージをベースイメージとして利用します。

```
ENV COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer
```

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#env

ENV命令はコンテナ内のサーバー環境変数を設定します。

```
COPY --from=composer:2.2 /usr/bin/composer /usr/bin/composer
```

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#copy

```
RUN apt-get update && \
  apt-get -y install --no-install-recommends git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  docker-php-ext-install intl pdo_mysql zip bcmath
```

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#run

Debian系のパッケージ管理ツールは `apt-get`, `apt` とありますが、Dockerfile内で `apt` を実行するとCLIインターフェース向けではないと警告が表示されるため、`apt-get`を使用します。

`apt-get update` インストール可能なパッケージの「一覧」を更新します。
実際のパッケージのインストール、アップグレードなどは行いません。

`apt-get -y install --no-install-recommends xxx` Laravelのインストールに必要なパッケージをインストールします。
下記のパッケージをインストールしておけばokだと思います。

- https://packages.debian.org/ja/sid/git
- https://packages.debian.org/ja/sid/unzip
- https://packages.debian.org/ja/sid/libzip-dev
- https://packages.debian.org/ja/sid/libicu-dev
- https://packages.debian.org/ja/sid/libonig-dev

`-y` オプションを付けることで問い合わせがあった場合はすべて「y」と答えます。

`--no-install-recommends` 不要なパッケージのインストールを防止します。

`apt-get clean && rm -rf /var/lib/apt/lists/*` ここはパッケージインストールで使用したキャッシュファイルを削除しています。

phpの公式Dockerイメージには、`docker-php-ext-install`, `docker-php-ext-enable`, `docker-php-ext-configure` のPHP拡張ライブラリを簡単に利用するための便利コマンドが予め用意されています。

`docker-php-ext-install intl pdo_mysql zip bcmath` PHPの拡張ライブラリをインストールしています。

- https://www.php.net/manual/ja/book.intl.php
- https://www.php.net/manual/ja/ref.pdo-mysql.php
- https://www.php.net/manual/ja/book.zip.php
- https://www.php.net/manual/ja/book.bc.php

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

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#copy

```
WORKDIR /data
```

https://matsuand.github.io/docs.docker.jp.onthefly/engine/reference/builder/#workdir

#### ./infra/php/php.ini を作成する

php.ini はPHPの設定ファイル

- PHPエラーメッセージの設定
- PHPエラーログの設定
- メモリ等の設定(お好みで)
- タイムゾーン設定
- 文字コード設定

```shell
[mac] $ touch infra/php/php.ini
```

```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 = /dev/stderr
default_charset = UTF-8

[Date]
date.timezone = Asia/Tokyo

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese
```

- [php.net ini.core.php](https://www.php.net/manual/ja/ini.core.php)
- [php.net mbstring.configuration](https://www.php.net/manual/ja/mbstring.configuration.php)
- [PHP7.4 ぼくのかんがえたさいきょうのphp.ini](https://qiita.com/ucan-lab/items/0d74378e1b9ba81699a9)

#### src ディレクトリを作成

bindマウントするためのsrcディレクトリを作成します。

```shell
[mac] $ mkdir src
```

#### build & up

- appコンテナの作成
- PHPのバージョン確認
- Laravelで必要なPHP拡張機能の確認

```shell
[mac] $ docker compose build
```

ビルドは、`docker-compose.yml` や `Dockerfile` を元にDockerイメージを作成します。
作成したDockerイメージからDockerコンテナを生成します。

```shell
[mac] $ docker compose up -d
```

- `docker compose` コマンドは `docker-compose.yml` があるディレクトリで実行します。
- `docker compose up` は `docker-compose.yml` に定義したサービスを起動します。
- `-d` 「デタッチド」モードでコンテナを起動します。
  - デフォルトは「アタッチド」モードで全てのコンテナログを画面上に表示
  - 「デタッチド」モードではバックグラウンドで動作

```shell
[mac] $ docker compose ps
NAME                           COMMAND                  SERVICE             STATUS              PORTS
docker-laravel-handson-app-1   "docker-php-entrypoi…"   app                 running             9000/tcp
```

docker-laravel-handson_app_1 コンテナの State が Up になっていたら正常に起動している状態です。

- `docker compose ps` コンテナ一覧を表示します。
  - Name: コンテナ名
  - Command: 最後に実行されたコマンド
  - State: 状態(Up)はコンテナが起動している状態
  - Ports: 9000/tcp(コンテナのポート)
    - 9000番のホストポートは公開されていないので、コンテナの外からはアクセスできない

#### appコンテナ内ミドルウェアのバージョン確認(コンテナに入ってコマンド実行)

作成したappコンテナの中に入ってPHP, Composerのバージョン、インストール済みの拡張機能を確認します。
[Laravel 9.xのサーバ要件](https://readouble.com/laravel/9.x/ja/deployment.html)に必要な拡張機能が入っていることを確認します。

```shell
[mac] $ docker compose exec app bash
ユーザー名@コンテナID:ディレクトリ#
root@927707a27bba:/data#

# PHPのバージョン確認
[app] # php -v
PHP 8.1.3 (cli) (built: Feb 18 2022 20:07:43) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies

# Composerのバージョン確認
[app] # composer -V
Composer version 2.2.6 2022-02-04 17:00:38

# インストール済みの拡張機能の一覧
[app] # php -m
[PHP Modules]
bcmath
Core
ctype
curl
date
dom
fileinfo
filter
ftp
hash
iconv
intl
json
libxml
mbstring
mysqlnd
openssl
pcre
PDO
pdo_mysql
pdo_sqlite
Phar
posix
readline
Reflection
session
SimpleXML
sodium
SPL
sqlite3
standard
tokenizer
xml
xmlreader
xmlwriter
zip
zlib

[Zend Modules]
```

`exit` でコンテナの外に出ます。

```shell
[app] $ exit
```

`control` + `d` でもコンテナから出られます。

#### 【補足】docker compose exec

appコンテナ内に入ってphpコマンドを実行しています。

- `docker compose exec` 実行中のコンテナ内で、コマンドを実行します。
- `app` サービス名(コンテナ名)を指定します。

#### PHPのバージョン確認(コンテナの外からコマンド実行)

コンテナの外から `php` コマンドを実行することもできます。

- `docker compose exec [サービス名] [実行したいコマンド]`

```shell
[mac] $ docker compose exec app php -v
PHP 8.1.3 (cli) (built: Feb 18 2022 20:07:43) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.1.3, Copyright (c) Zend Technologies
```

#### 結果にコミット

```shell
[mac] $ git status
Untracked files:
  (use "git add <file>..." to include in what will be committed)
	docker-compose.yml
	infra/php/Dockerfile
	infra/php/php.ini

[mac] $ git add .
[mac] $ git status
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	new file:   docker-compose.yml
	new file:   infra/php/Dockerfile
	new file:   infra/php/php.ini

[mac] $ git commit -m "feat create docker app container"
[main e1e6eeb] feat create docker app container
 3 files changed, 48 insertions(+)
 create mode 100644 docker-compose.yml
 create mode 100644 infra/php/Dockerfile
 create mode 100644 infra/php/php.ini

[mac] $ git log
```

#### コンテナを破棄する

docker-compose.yml を変更するので、一度コンテナを破棄しておきます。

```shell
[mac] $ docker compose down
```

### ウェブサーバー(web)コンテナを作る

nginxウェブサーバーコンテナを作成します。
nginxのベースイメージをそのまま利用します。

- [Docker Hub nginx](https://hub.docker.com/_/nginx)

#### 【補足】ディレクトリ構成

```shell-session
.
├── infra
│   └── nginx
│       └── default.conf # nginxの設定ファイル
├── src
│  └── public # 動作確認用に作成
│       ├── index.html # HTML動作確認用
│       └── phpinfo.php # PHP動作確認用
└─── docker-compose.yml
```

#### docker-compose.yml へ追記する

- ポート転送の設定(今回は8080ポートにする)
- タイムゾーンの設定

```yaml
version: "3.9"
services:
  app:
    build: ./infra/php
    volumes:
      - ./src:/data

  # 追記
  web:
    image: nginx:1.20-alpine
    ports:
      - 8080:80
    volumes:
      - ./src:/data
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
    working_dir: /data
```

- `docker-compose.yml` ファイルはインデント(半角スペース)が意味を持ちます。注意してコピペしてください。
- `app` コンテナの設定と同じインデントレベルで貼り付けます。

#### 【補足】docker-compose.yml

```
    image: nginx:1.20-alpine
```

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/#image

コンテナを起動させるイメージを指定します。
[nginx | Docker Hub](https://hub.docker.com/_/nginx)を指定してます。
今回は公式のnginxイメージをそのまま利用しています。(Dockerfileは不要)

ちなみにnginxは`1.10`, `1.12` 等の偶数のバージョンが安定バージョンになります。
特に理由がなければ偶数バージョンをご利用ください。

```
    ports:
      - 8080:80
```

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/#ports

nginxへ外(ホスト側)からコンテナ内へアクセスさせるため公開用のポートを設定します。
`ホスト側:コンテナ側` と設定します。

```
    volumes:
      - ./src:/data
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
```

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/#volumes

ホスト側にあるディレクトリ、ファイルをコンテナ内へマウントさせています。

#### infra/nginx/default.conf を作成する

```shell
[mac] $ mkdir infra/nginx
[mac] $ touch infra/nginx/default.conf
```

Laravel公式にnginxの設定例が用意されているので、こちらを流用します。
https://readouble.com/laravel/8.x/ja/deployment.html

```default.conf
server {
    listen 80;
    server_name example.com;
    root /data/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`, `fastcgi_pass` ドキュメントルート設定を書き換えてます。
nginxの設定を詳しく知りたい方は下記の記事がオススメです。

- [nginx beginners_guide](http://nginx.org/en/docs/beginners_guide.html)
- [Nginx設定のまとめ](https://qiita.com/syou007/items/3e2d410bbe65a364b603)
- [nginxについてまとめ(設定編)](https://qiita.com/morrr/items/7c97f0d2e46f7a8ec967)

#### build & up

```shell
[mac] $ docker compose up -d
```

- `docker-compose` コマンドは `docker-compose.yml` があるディレクトリで実行します。

```shell
[mac] $ docker compose ps
NAME                           COMMAND                  SERVICE             STATUS              PORTS
docker-laravel-handson-app-1   "docker-php-entrypoi…"   app                 running             9000/tcp
docker-laravel-handson-web-1   "/docker-entrypoint.…"   web                 running             0.0.0.0:8080->80/tcp
```

docker-laravel-handson_web_1 コンテナの State が Up になっていたら正常に起動している状態です。

また、`Ports` の項目が、appコンテナは`9000/tcp` でwebコンテナは `0.0.0.0:8080->80/tcp` と表示形式が異なってます。
これはホスト上の8080番ポートをコンテナの80番ポートへ割り当てています。

#### nginxのバージョン確認

```shell
[mac] $ docker compose exec web nginx -v
nginx version: nginx/1.20.2
```

#### webコンテナの確認

- webコンテナの動作確認
- HTMLとPHPが表示されるか

```shell
[mac] $ mkdir src/public
[mac] $ echo "Hello World" > src/public/index.html
[mac] $ echo "<?php phpinfo();" > src/public/phpinfo.php
```

http://localhost:8080/index.html

![](https://i.imgur.com/UyI1dSU.png)

「Hello World」が表示されることを確認する。
webサーバーが正しく動作することを確認できました。

http://localhost:8080/phpinfo.php

![](https://i.imgur.com/sXrjn7x.png)

phpinfoの情報が表示されることを確認する。
webサーバーがappサーバーへphpを実行させ結果を返してくれることを確認できました。

```shell
[mac] $ rm -rf src/*
```

確認用に作成したHTML, PHPファイルは不要なので削除します。

#### 結果にコミット

```shell
[mac] $ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   docker-compose.yml

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	infra/nginx/default.conf

[mac] $ git add .
[mac] $ git status
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   docker-compose.yml
	new file:   infra/nginx/default.conf

[mac] $ git commit -m "feat create docker web container"
[main 1e26387] feat create docker web container
 2 files changed, 41 insertions(+)
 create mode 100644 infra/nginx/default.conf

[mac] $ git log
```

### Laravelをインストールする

- app コンテナに入り、Laravelをインストール
- welcomeページが表示されるか

```shell
[mac] $ docker compose exec app bash
[app] $ composer create-project --prefer-dist "laravel/laravel=9.*" .
[app] $ chmod -R 777 storage bootstrap/cache
[app] $ php artisan -V
Laravel Framework 9.1.0

[app] $ exit
```

- `composer create-project --prefer-dist`
  - `--prefer-dist` zipでダウンロードするため高速
  - [composer の–prefer-distってよく使うけど何してる？](https://kin29.info/composer-%E3%81%AE-prefer-dist%E3%81%A3%E3%81%A6%E3%82%88%E3%81%8F%E4%BD%BF%E3%81%86%E3%81%91%E3%81%A9%E4%BD%95%E3%81%97%E3%81%A6%E3%82%8B%EF%BC%9F/)
- `composer create-project` コマンドでメモリ足りない問題の対策
  - [composer update でメモリオーバーする場合の対策](https://qiita.com/ucan-lab/items/af39b71c6eb304ddf696)
- `artisan` について
  - Laravelが用意しているコマンドラインインターフェイス
  - https://readouble.com/laravel/9.x/ja/artisan.html
  - `php artisan list` 使用可能なコマンド一覧を表示

### Laravel ウェルカム画面の表示

http://localhost:8080

![](https://i.imgur.com/YZURHKH.png)
![](https://i.imgur.com/fUgy7MM.png)

LaravelのWelcome画面が表示されることを確認する。ライトモードとダークモードで色合いが変わります。

#### 結果にコミット

```shell
[mac] $ git status
[mac] $ git add .
[mac] $ git status
[mac] $ git commit -m "feat laravel install"
[mac] $ git log
```

今回は大量のファイルがインストールで生成されているので、差分は省略してます。

#### コンテナを破棄する

docker-compose.yml を変更するので、一度コンテナを破棄しておきます。

```shell
[mac] $ docker compose down
```

### データベース(db)コンテナを作る

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

- [Docker Hub| mysql/mysql-server](https://hub.docker.com/r/mysql/mysql-server)

#### 【補足】mysql/mysql-serverイメージ

- https://hub.docker.com/_/mysql
- https://hub.docker.com/r/mysql/mysql-server

Docker公式がメンテしている `mysql` イメージと OracleのMySQLチームがメンテしている `mysql/mysql-server` の2種類あります。
オススメは `mysql/mysql-server` の方です。
`mysql` の方だとM1 Macが動作しないといった報告があります。

- [docker-compose MySQL8.0 のDBコンテナを作成する](https://qiita.com/ucan-lab/items/b094dbfc12ac1cbee8cb)

#### 【補足】ディレクトリ構成

```shell-session
.
├── infra
│   └── mysql
│       ├── Dockerfile
│       └── my.cnf # MySQLの設定ファイル
└── docker-compose.yml
```

#### docker-compose.yml へ追記する

- データベース名やユーザー名等の接続情報とタイムゾーンの設定は環境変数で渡す
- トップレベルvolumeを使用してデータの永続化

```yaml
version: "3.9"
services:
  app:
    build: ./infra/php
    volumes:
      - ./src:/data

  web:
    image: nginx:1.20-alpine
    ports:
      - 8080:80
    volumes:
      - ./src:/data
      - ./infra/nginx/default.conf:/etc/nginx/conf.d/default.conf
    working_dir: /data

  # 追記
  db:
    build: ./infra/mysql
    volumes:
      - db-store:/var/lib/mysql

volumes:
  db-store:
```

- `docker-compose.yml` ファイルはインデント(半角スペース)が意味を持ちます。注意してコピペしてください。
- `web` コンテナの設定と同じインデントレベルで `db` コンテナの設定を貼り付けます。
- `volumes` はトップレベル(servicesと同じレベル)に貼り付けます。
- [DockerのMySQLイメージ起動時に渡す環境変数](https://qiita.com/nanakenashi/items/180941699dc7ba9d0922)
- [Docker for Macのmount遅い問題まとめ](https://blog.hanhans.net/2017/05/23/docker-for-mac-slow/)
- [Performance tuning for volume mounts (shared filesystems)](https://docs.docker.com/docker-for-mac/osxfs-caching/)

#### ./infra/mysql/Dockerfile を作成する

```shell
[mac] $ mkdir infra/mysql
[mac] $ touch infra/mysql/Dockerfile
```

下記のコードを丸ごとコピーして Dockerfile へ貼り付けてください。

```dockerfile:infra/mysql/Dockerfile
FROM mysql/mysql-server:8.0

ENV MYSQL_DATABASE=laravel \
  MYSQL_USER=phper \
  MYSQL_PASSWORD=secret \
  MYSQL_ROOT_PASSWORD=secret \
  TZ=Asia/Tokyo

COPY ./my.cnf /etc/my.cnf
RUN chmod 644 /etc/my.cnf
```

コメントでいただいたのですが、Windows環境でボリュームマウントを行うと、ファイルパーミッションが777となるようです。
`my.cnf` に書き込み権限が付いてるとMySQLの起動時にエラーが発生します。
その対策としてボリュームマウントではなくDockerfileを作成して `my.cnf` ファイルコピー、読み取り専用に権限変更してます。

- [mysqld: [Warning] World-writable config file '/etc/mysql/conf.d/my.cnf' is ignored.](https://sunday-morning.app/posts/2020-07-31-mysqld-warning-world-writable-config-file-etc-mysql-conf-d-my-cnf-is-ignored)

#### infra/mysql/my.cnf を作成する

細かい解説はしませんが、主に下記の設定をしています。
ハンズオンでただ動かす程度ですから設定ファイル自体必要ないと思います。

- 文字コード、照合順序の設定
- タイムゾーンの設定
- ログ設定

```shell
[mac] $ touch infra/mysql/my.cnf
```

```my.cnf
[mysqld]
# default
skip-host-cache
skip-name-resolve
datadir = /var/lib/mysql
socket = /var/lib/mysql/mysql.sock
secure-file-priv = /var/lib/mysql-files
user = mysql

pid-file = /var/run/mysqld/mysqld.pid

# character set / collation
character_set_server = utf8mb4
collation_server = utf8mb4_ja_0900_as_cs_ks

# 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
```

`default` コメント部分はコンテナに元々入っていた設定を踏襲しています。

文字コードと照合順序は、 `utf8mb4`, `utf8mb4_ja_0900_as_cs_ks` を選択するのが現状良いとされています。

- [utf8とutf8mb4の違い【MySQL】](https://penpen-dev.com/blog/mysql-utf8-utf8mb4/)
- [MySQL 8.0のCharset utf8mb4での日本語環境で使うCollationで文字比較をしてみる](https://kazuhira-r.hatenablog.com/entry/2021/05/08/232717)

タイムゾーンでSYSTEMを設定するとOSのタイムゾーンが設定されます。

### build & up

```shell
[mac] $ docker compose build
[mac] $ docker compose up -d
```

- `docker compose` コマンドは `docker-compose.yml` があるディレクトリで実行します。

```shell
[mac] $ docker compose ps
NAME                           COMMAND                  SERVICE             STATUS               PORTS
docker-laravel-handson-app-1   "docker-php-entrypoi…"   app                 running              9000/tcp
docker-laravel-handson-db-1    "/entrypoint.sh mysq…"   db                  running (starting)   33060-33061/tcp
docker-laravel-handson-web-1   "/docker-entrypoint.…"   web                 running              0.0.0.0:8080->80/tcp
```

docker-laravel-handson_db_1 コンテナの State が Up になっていたら正常に起動している状態です。

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

### マイグレーション実行(エラーが発生します)

```shell
[mac] $ docker compose exec app bash
[app] $ php artisan migrate

   Illuminate\Database\QueryException 

  SQLSTATE[HY000] [2002] Connection refused (SQL: select * from information_schema.tables where table_schema = laravel and table_name = migrations and table_type = 'BASE TABLE')

  at vendor/laravel/framework/src/Illuminate/Database/Connection.php:712
    708▕         // If an exception occurs when attempting to run a query, we'll format the error
    709▕         // message to include the bindings with SQL, which will make this exception a
    710▕         // lot more helpful to the developer instead of just the database's errors.
    711▕         catch (Exception $e) {
  ➜ 712▕             throw new QueryException(
    713▕                 $query, $this->prepareBindings($bindings), $e
    714▕             );
    715▕         }
    716▕     }

      +36 vendor frames 
  37  artisan:37
      Illuminate\Foundation\Console\Kernel::handle(Object(Symfony\Component\Console\Input\ArgvInput), Object(Symfony\Component\Console\Output\ConsoleOutput))


[app] $ exit
```

`SQLSTATE[HY000] [2002] Connection refused` このエラーはよく見るMySQLのエラーです。
MySQLに接続拒否されたエラーなので、この場合は大体MySQLへの接続設定に誤りがあります。

`src/.env` のDB接続設定を修正する。

```shell
[mac] $ code src/.env
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=phper
DB_PASSWORD=secret
```

`src/.env.example` も同様にDB接続設定を修正しておきます。

```shell
[mac] $ vim src/.env.example
DB_CONNECTION=mysql
DB_HOST=db
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=phper
DB_PASSWORD=secret
```

#### 【補足】DB_HOST=db

`php artisan migrate` は `app` サービスのコンテナが `php` を実行しています。
`app` から `db` サービスのコンテナへ通信できることが必要です。

```.env
DB_HOST=127.0.0.1
```

これだと app コンテナ内のデータベースに接続しようと試みます。
appコンテナ内にデータベースはないので、もちろんエラーになります。


```shell
$ docker network list
NETWORK ID     NAME                             DRIVER    SCOPE
be6ba90aeade   bridge                           bridge    local
98a3fc98010d   docker-laravel-handson_default   bridge    local
be953fa99c81   host                             host      local
a8e65b8280d4   none                             null      local
```

`docker-laravel-handson_default` のデフォルトネットワークが構築されてます。
`docker network inspect` コマンドでネットワークの詳細を見れます。

```shell
$ docker network inspect docker-laravel-handson_default
[
    {
        "Name": "docker-laravel-handson_default",
        "Id": "98a3fc98010ddb61798d623c544d9a2a79762e85f94fc7c1660f2a76c83da8ed",
        "Created": "2022-02-21T16:02:09.872079294Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "1be61fbec051bd6f995a78ed3fe39b088a864f5eea01448e4fada84896a8cfee": {
                "Name": "docker-laravel-handson-app-1",
                "EndpointID": "1a920f7a384c0b53578c2b1f66cc80501d4879e0d9edac0e97dbbdb5c8b964bf",
                "MacAddress": "02:42:ac:15:00:03",
                "IPv4Address": "172.21.0.3/16",
                "IPv6Address": ""
            },
            "2485795092e86232ea8cbe91373f7cf71c568f25382fcde6a0e9252dd5b0a95b": {
                "Name": "docker-laravel-handson-db-1",
                "EndpointID": "4073d7ee440ba55e167e319e85ec05e9681a100e2505abd2debbfab09144daee",
                "MacAddress": "02:42:ac:15:00:02",
                "IPv4Address": "172.21.0.2/16",
                "IPv6Address": ""
            },
            "e1fa6a28aff5b6de5045e88e116e8ef1f978e0b7327b977de3771a4100b41c75": {
                "Name": "docker-laravel-handson-web-1",
                "EndpointID": "ff568293928f6fbf7c017548c3aff80bd91108a023dfc291d4eaf035c08dc44e",
                "MacAddress": "02:42:ac:15:00:04",
                "IPv4Address": "172.21.0.4/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "default",
            "com.docker.compose.project": "docker-laravel-handson",
            "com.docker.compose.version": "2.1.1"
        }
    }
]
```

`Containers` を見ると `app`, `web`, `db` コンテナがデフォルトネットワークに所属していることが分かります。
コンテナはネットワークを共有している場合のみ相互に通信できます。

各コンテナはホスト名を検索し、`app`, `web`, `db` のサービス名から適切なコンテナのIPアドレスを取得できます。

```.env
DB_HOST=db
```

まとめるとLaravelのartisanコマンドはappのコンテナで実行され、dbコンテナのIPを解決してdbコンテナと通信できます。

- https://docs.docker.com/compose/networking
- https://docs.docker.com/compose/compose-file/compose-file-v3/#network-configuration-reference
- https://knowledge.sakura.ad.jp/23899

### マイグレーション実行(再実行)

```shell
[mac] $ docker compose exec app bash
[app] $ php artisan migrate

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (58.09ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (42.39ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (70.60ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (62.23ms)
```

新しいターミナルを開いて、下記のコマンドを実行します。

```shell
[mac] $ docker compose exec db bash
[db] $ mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE
[mysql] > show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| laravel            |
+--------------------+
2 rows in set (0.00 sec)

[mysql] > show tables;
+------------------------+
| Tables_in_laravel      |
+------------------------+
| failed_jobs            |
| migrations             |
| password_resets        |
| personal_access_tokens |
| users                  |
+------------------------+
5 rows in set (0.00 sec)

[mysql] > desc users;
+-------------------+-----------------+------+-----+---------+----------------+
| Field             | Type            | Null | Key | Default | Extra          |
+-------------------+-----------------+------+-----+---------+----------------+
| id                | bigint unsigned | NO   | PRI | NULL    | auto_increment |
| name              | varchar(255)    | NO   |     | NULL    |                |
| email             | varchar(255)    | NO   | UNI | NULL    |                |
| email_verified_at | timestamp       | YES  |     | NULL    |                |
| password          | varchar(255)    | NO   |     | NULL    |                |
| remember_token    | varchar(100)    | YES  |     | NULL    |                |
| created_at        | timestamp       | YES  |     | NULL    |                |
| updated_at        | timestamp       | YES  |     | NULL    |                |
+-------------------+-----------------+------+-----+---------+----------------+
8 rows in set (0.01 sec)

[mysql] > SELECT * FROM users;
Empty set (0.01 sec)
```

MySQLを開いているターミナルは閉じずに残しておきましょう。

### 試しにデータを作ってみる

```shell
[mac] $ docker compose exec app bash
[app] $ php artisan tinker

# 下記のコードはコピペして実行できます。

$user = new App\Models\User();
$user->name = 'phper';
$user->email = 'phper@example.com';
$user->password = Hash::make('secret');
$user->save();
```

MySQLを開いているターミナルに戻って下記のSQLを実行します。

```shell
[mysql] > SELECT * FROM users\G
*************************** 1. row ***************************
               id: 1
             name: phper
            email: phper@example.com
email_verified_at: NULL
         password: $2y$10$/es6/J88KaxbbwKElD4qveMz/W5HdEb2T.r8loJdkLsqlGvHakUp6
   remember_token: NULL
       created_at: 2022-02-21 16:10:17
       updated_at: 2022-02-21 16:10:17
1 row in set (0.00 sec)
```

#### 結果にコミット

```shell
[mac] $ git status
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        modified:   docker-compose.yml
        modified:   src/.env.example

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        infra/mysql/Dockerfile
        infra/mysql/my.cnf

[mac] $ git add .
[mac] $ git status

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        modified:   docker-compose.yml
        new file:   infra/mysql/Dockerfile
        new file:   infra/mysql/my.cnf
        modified:   src/.env.example

[mac] $ git commit -m "feat create docker db container"
[mac] $ git log
```

### GitHubにpush

```shell
[mac] $ git push
```

リモートリポジトリへpushされていることを確認します。

- https://github.com/ユーザー名/docker-laravel-handson

## Docker環境の再構築

### Docker環境の破棄

#### コンテナの停止、ネットワーク・名前付きボリューム・コンテナイメージ、未定義コンテナを削除

```shell
[mac] $ docker compose down --rmi all --volumes --remove-orphans
```

- [《滅びの呪文》Docker Composeで作ったコンテナ、イメージ、ボリューム、ネットワークを一括完全消去する便利コマンド](https://qiita.com/suin/items/19d65e191b96a0079417)

#### 作業ディレクトリの削除

プロジェクトを削除するので、GUIエディタは閉じておきましょう。

```shell
[mac] $ cd ..
[mac] $ rm -rf docker-laravel-handson
```

**Visual Studio Code等のGUIエディタで開いている場合は、一度エディタを終了しましょう。**

### 環境の再構築

#### GitHubからリポジトリをクローン

** 自身のリポジトリ先に適宜変更してください **

```shell
[mac] $ git clone git@github.com:ucan-lab/docker-laravel-handson.git
[mac] $ cd docker-laravel-handson
[mac] $ docker compose up -d
```

http://localhost:8080

![](https://i.imgur.com/PT1rwJq.png)

`/data/public/../vendor/autoload.php` を開くのに失敗してエラーになっていることを確認します。
git cloneが終わった状態では `app` コンテナ内に `/data/vendor` ディレクトリが存在しないためです。

#### Laravelインストール

app コンテナに入ります。

```shell
[mac] $ docker compose exec app bash
```

書き込み権限がないとキャッシュやログにエラーを書き込めないので、権限を付与しておきます。

```shell
[app] $ chmod -R 777 storage bootstrap/cache
```

`vendor` ディレクトリへライブラリ群をインストールします。
`composer.lock` ファイルを参照します。

```shell
[app] $ composer install
```

画面を開いて確認します。

http://localhost:8080

![](https://i.imgur.com/6m7Babk.png)

500 SERVER ERROR だと何が原因なのか分かりません。
ログファイルを見てエラーを確認します。

```shell
[app] $ cat storage/logs/laravel.log

...

[2022-02-15 15:07:29] production.ERROR: No application encryption key has been specified. {"exception":"[object] (Illuminate\\Encryption\\MissingAppKeyException(code: 0): No application encryption key has been specified. at /data/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php:79)
[stacktrace]
#0 /data/vendor/laravel/framework/src/Illuminate/Support/helpers.php(302): Illuminate\\Encryption\\EncryptionServiceProvider->Illuminate\\Encryption\\{closure}(NULL)
#1 /data/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php(81): tap(NULL, Object(Closure))
#2 /data/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php(60): Illuminate\\Encryption\\EncryptionServiceProvider->key(Array)
#3 /data/vendor/laravel/framework/src/Illuminate/Encryption/EncryptionServiceProvider.php(32): Illuminate\\Encryption\\EncryptionServiceProvider->parseKey(Array)
#4 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(873): Illuminate\\Encryption\\EncryptionServiceProvider->Illuminate\\Encryption\\{closure}(Object(Illuminate\\Foundation\\Application), Array)
#5 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(758): Illuminate\\Container\\Container->build(Object(Closure))
#6 /data/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(855): Illuminate\\Container\\Container->resolve('encrypter', Array, true)
#7 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(694): Illuminate\\Foundation\\Application->resolve('encrypter', Array)
#8 /data/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(840): Illuminate\\Container\\Container->make('encrypter', Array)
#9 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(1027): Illuminate\\Foundation\\Application->make('encrypter')
#10 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(947): Illuminate\\Container\\Container->resolveClass(Object(ReflectionParameter))
#11 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(908): Illuminate\\Container\\Container->resolveDependencies(Array)
#12 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(758): Illuminate\\Container\\Container->build('App\\\\Http\\\\Middle...')
#13 /data/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(855): Illuminate\\Container\\Container->resolve('App\\\\Http\\\\Middle...', Array, true)
#14 /data/vendor/laravel/framework/src/Illuminate/Container/Container.php(694): Illuminate\\Foundation\\Application->resolve('App\\\\Http\\\\Middle...', Array)
#15 /data/vendor/laravel/framework/src/Illuminate/Foundation/Application.php(840): Illuminate\\Container\\Container->make('App\\\\Http\\\\Middle...', Array)
#16 /data/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(206): Illuminate\\Foundation\\Application->make('App\\\\Http\\\\Middle...')
#17 /data/vendor/laravel/framework/src/Illuminate/Foundation/Http/Kernel.php(180): Illuminate\\Foundation\\Http\\Kernel->terminateMiddleware(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Response))
#18 /data/public/index.php(55): Illuminate\\Foundation\\Http\\Kernel->terminate(Object(Illuminate\\Http\\Request), Object(Illuminate\\Http\\Response))
#19 {main}
"} 
```

例外が発生した時のログは下記のフォーマットで出力されます。

```storage/logs/laravel.log
[YYYY-MM-DD HH:II:SS] 環境.ログレベル: ログメッセージ
トレースログ
```

トレースログは例外が発生した場所から呼び出し元の場所を遡って表示してくれます。
一番大事なのは最初のログメッセージです。

```storage/logs/laravel.log
1[2022-02-15 15:07:29] production.ERROR: No application encryption key has been specified. 
```

「アプリケーション暗号化キーが指定されていません。」というエラーになります。
`.env` の `APP_KEY` に値が設定されてないと表示されるエラーです。

```shell
[app] $ ls -l
total 328
drwxr-xr-x 25 root     root        800 Feb 15 15:05 .
drwxr-xr-x  1 root     root       4096 Feb 15 15:05 ..
-rw-r--r--  1 root     root        258 Feb 15 15:03 .editorconfig
-rw-r--r--  1 root     root        897 Feb 15 15:03 .env.example
-rw-r--r--  1 root     root        152 Feb 15 15:03 .gitattributes
-rw-r--r--  1 root     root        207 Feb 15 15:03 .gitignore
-rw-r--r--  1 root     root        175 Feb 15 15:03 .styleci.yml
-rw-r--r--  1 root     root       3958 Feb 15 15:03 README.md
drwxr-xr-x  7 root     root        224 Feb 15 15:03 app
-rwxr-xr-x  1 root     root       1686 Feb 15 15:03 artisan
drwxr-xr-x  4 root     root        128 Feb 15 15:03 bootstrap
-rw-r--r--  1 root     root       1746 Feb 15 15:03 composer.json
-rw-r--r--  1 root     root     285701 Feb 15 15:03 composer.lock
drwxr-xr-x 17 www-data www-data    544 Feb 15 15:03 config
drwxr-xr-x  6 root     root        192 Feb 15 15:03 database
drwxr-xr-x  4 www-data www-data    128 Feb 15 15:03 lang
-rw-r--r--  1 root     root        473 Feb 15 15:03 package.json
-rw-r--r--  1 root     root       1175 Feb 15 15:03 phpunit.xml
drwxr-xr-x  6      101 ssh         192 Feb 15 15:03 public
drwxr-xr-x  5 root     root        160 Feb 15 15:03 resources
drwxr-xr-x  6 root     root        192 Feb 15 15:03 routes
drwxr-xr-x  5 root     root        160 Feb 15 15:03 storage
drwxr-xr-x  6 root     root        192 Feb 15 15:03 tests
drwxr-xr-x 44 root     root       1408 Feb 15 15:06 vendor
-rw-r--r--  1 root     root        559 Feb 15 15:03 webpack.mix.js
```

`.env.example` はありますが、 `.env` ファイルが存在しません。
理由は `.env` はGit管理対象外に設定されているからです。

```shell
$ cat .gitignore
/node_modules
/public/hot
/public/storage
/storage/*.key
/vendor
.env
.env.backup
.phpunit.result.cache
docker-compose.override.yml
Homestead.json
Homestead.yaml
npm-debug.log
yarn-error.log
/.idea
/.vscode
```

そもそも `.env` ファイルがないのが問題なので、`.env.example` を元にコピーして作成します。
(`composer create-project` 時は `.env` は作成されますが、 `composer install` 時は `.env` ファイルは作成されません。)

```shell
[app] $ cp .env.example .env
```

http://localhost:8080

![](https://i.imgur.com/UmE779b.png)

エラー画面の見た目が変わりました。
これは `.env` で `APP_DEBUG=true` が設定されているためです。

エラーメッセージはログと同じく `.env` に `APP_KEY=` の値がないと出ています。
アプリケーションキーはこのコマンドで生成できます。

```shell
[app] $ php artisan key:generate
Application key set successfully.
```

http://localhost:8080

![](https://i.imgur.com/gjuXbBz.png)

とりあえず、Welcome画面が表示されました。

続いて、 `public/storage` から `storage/app/public` へのシンボリックリンクを張ります。
システムで生成したファイル等をブラウザからアクセスできるよう公開するためにシンボリックリンクを張ってます。

```shell
[app] $ php artisan storage:link
```

最後にマイグレーションを実行して適用されればOKです。

```shell
[app] $ php artisan migrate

Migration table created successfully.
Migrating: 2014_10_12_000000_create_users_table
Migrated:  2014_10_12_000000_create_users_table (49.19ms)
Migrating: 2014_10_12_100000_create_password_resets_table
Migrated:  2014_10_12_100000_create_password_resets_table (84.15ms)
Migrating: 2019_08_19_000000_create_failed_jobs_table
Migrated:  2019_08_19_000000_create_failed_jobs_table (39.74ms)
Migrating: 2019_12_14_000001_create_personal_access_tokens_table
Migrated:  2019_12_14_000001_create_personal_access_tokens_table (54.08ms)
```

お疲れ様でした🙌🙌 コンテナを停止して終了してください。

```shell
[app] $ exit
[mac] $ docker compose down
```

## オマケ

### GitHub Actions を使ってビルドテストを書く

```shell
$ git switch -c laravel-testing
```

プリリクエストを作りたいので、ブランチを切ります。

```shell
$ mkdir -p .github/workflows
$ touch .github/workflows/laravel-testing.yml
```

GitHub Actions のワークフロー設定ファイルを追加します。

```yaml:.github/workflows/laravel-testing.yml
name: Laravel Testing

on:
  pull_request:

jobs:
  laravel-testing:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2

      - name: Docker Version
        run: docker version

      - name: Build Docker Images
        run: docker-compose build

      - name: Create & Start Docker Containers
        run: docker-compose up -d

      - name: OS Version
        run: |
          docker-compose exec -T app cat /etc/os-release
          docker-compose exec -T app cat /etc/debian_version

      - name: PHP Version
        run: docker-compose exec -T app php --version

      - name: Composer Version
        run: docker-compose exec -T app composer --version

      - name: Install Dependencies
        run: docker-compose exec -T app composer install

      - name: Laravel Version
        run: docker-compose exec -T app php artisan --version

      - name: Laravel Setting
        run: |
          docker-compose exec -T app cp .env.example .env
          docker-compose exec -T app php artisan key:generate

      - name: Laravel Migrate Testing
        run: docker-compose exec -T app php artisan migrate

      - name: Laravel Rollback Testing
        run: docker-compose exec -T app php artisan migrate:refresh

      - name: Laravel Seeding Testing
        run: docker-compose exec -T app php artisan db:seed

      - name: Laravel PHPUnit Testing
        run: docker-compose exec -T app php artisan test
```

```
$ git add .
$ git commit -m "feat laravel testing on github actions"
$ git push -u origin HEAD

...

remote:      https://github.com/ucan-lab/docker-laravel-handson/pull/new/laravel-testing
```

ターミナルに表示されるURLを開いてプルリクエスト作成します。

![](https://i.imgur.com/BhBqsic.png)

Detailsからワークフローの詳細ログを確認できます。

![](https://i.imgur.com/uZzzKJZ.png)

### 【補足】GitHub Actionsの無料枠

Public Repositoryの場合はActionsを無料で使用できます。
Privateの場合は無料枠を超えると追加で課金が必要なので注意してください。

### 【補足】Laravelのログをコンテナログに表示する

`src/.env` を修正する。

```
LOG_CHANNEL=stderr
```

`src/routes/web.php`

```
Route::get('/', function () {
    logger('welcome route.');
    return view('welcome');
});
```

http://localhost:8080

```shell
$ docker compose logs
# -f でログウォッチ
$ docker compose logs -f
# サービス名を指定してログを表示
$ docker compose logs -f app
```

### 【補足】MySQLクライアントツールで接続したい

`docker-compose.yml` の `db` サービスに下記設定を追記して、コンテナを再起動して設定を反映してください。

```yaml
    ports:
      - 33060:3306
```

dbコンテナへのポート公開設定がないとホストからアクセスが行えません。
MySQLのデフォルトポートは`3306`ですが、ポートが被らないように設定例では`33060`ポートを公開してます。

MySQLクライアントツールは「[Sequel Ace](https://github.com/Sequel-Ace/Sequel-Ace)」がオススメです。

- [ついにSequel Proの後継アプリ登場！Sequel AceでMySQLに接続する](https://qiita.com/ucan-lab/items/b1304eee2157dbef7774)

```
Host: localhost
Username: phper
Password: secret
Database: laravel
Port: 33060
```

## 【補足】Windowsエラーメモ

### Error response from daemon

```shell
$ docker compose up -d --build
docker: Error response from daemon: Get https://registry-1.docker.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers).
```

#### 解決策をいくつか

- Docker For Windowsを再起動する
- [Dockerでpullやrunを行ったときイメージダウンロードができなくなった問題の解決法](https://satoyashiki.hatenablog.com/entry/2017/04/01/175604)
- [Proxy環境下のDockerトラブルシューティング](https://qiita.com/tora470/items/cd9335f5ce638e11bb97)
- [さわって理解する Docker 入門](https://www.ogis-ri.co.jp/otc/hiroba/technical/docker/part5.html)

## 【補足】マイグレーションエラーの補足

### Host '172.27.0.2' is not allowed to connect to this MySQL server

```shell-session
$ php artisan migrate
   Illuminate\Database\QueryException  : SQLSTATE[HY000] [1130] Host '172.27.0.2' is not allowed to connect to this MySQL server (SQL: select * from information_schema.tables where table_schema = homestead and table_name = migrations and table_type = 'BASE TABLE')

  at /work/vendor/laravel/framework/src/Illuminate/Database/Connection.php:664
    660|         // If an exception occurs when attempting to run a query, we'll format the error
    661|         // message to include the bindings with SQL, which will make this exception a
    662|         // lot more helpful to the developer instead of just the database's errors.
    663|         catch (Exception $e) {
  > 664|             throw new QueryException(
    665|                 $query, $this->prepareBindings($bindings), $e
    666|             );
    667|         }
    668| 

  Exception trace:

  1   PDOException::("SQLSTATE[HY000] [1130] Host '172.27.0.2' is not allowed to connect to this MySQL server")
      /work/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  2   PDO::__construct("mysql:host=db;port=3306;dbname=homestead", "homestead", "secret", [])
      /work/vendor/laravel/framework/src/Illuminate/Database/Connectors/Connector.php:70

  Please use the argument -v to see more details.
```

このエラーが発生した場合は `my.cnf` を作成する前に `docker compose up -d` でビルドしてしまった可能性が高いです。

```shell-session
$ docker compose down --volumes --rmi all
$ docker compose up -d --build
```

設定ファイルがない状態でMySQLの初期化が行われたでデータが永続化されてしまってるので一度ボリューム毎削除してビルドし直せばokです。

## 豆知識

### 【補足】Makefileを使おう

```Makefile
install:
	@make clean
	@make build
	@make up
	docker compose exec app composer install
	docker compose exec app cp .env.example .env
	docker compose exec app php artisan key:generate
	docker compose exec app php artisan storage:link
	docker compose exec app chmod -R 777 storage bootstrap/cache
	@make fresh
clean:
	docker compose down --rmi all --volumes --remove-orphans
build:
	docker compose build --no-cache --force-rm
up:
	docker compose up -d
down:
	docker compose down
fresh:
	docker compose exec app php artisan migrate:fresh --seed
app:
	docker compose exec app bash
sql:
	docker compose exec db bash -c 'mysql -u $$MYSQL_USER -p$$MYSQL_PASSWORD $$MYSQL_DATABASE'
```

- コマンドの前の空白はスペースではなく、タブで書く必要があります。
- `@make` を使うとエコーバックされなくなります。
- MakefileはC/C++のコンパイル定義に使われます。
  - エイリアス的に使うのは正しい使い方ではないです。

```
$ make install
```

とコマンドを打つとこれ一発で環境構築をやってくれます。
README.mdにコマンドの手順を書いてもいいですが、Makefileに書いてあった方が楽かなと思ってこっちを採用してます。

使用頻度の高いコマンドを定義しておくと良いです。

### 【補足】docker compose と docker-compose コマンドの違い

2021/03/10 Compose CLIが技術プレビュー版として利用可能になりました。(Docker Desktop for Mac, for Windows 3.2.1以降で利用可能です)

技術プレビュー版では本番環境で用いることは推奨されていませんが、今度は `docker compose` コマンドに移行になります。(`docker-compose` のほぼすべてのコマンドがリプレース済みです。)

- https://github.com/docker/compose-cli
- https://docs.docker.com/compose/cli-command
- https://docs.docker.com/compose/cli-command-compatibility
- https://matsuand.github.io/docs.docker.jp.onthefly/compose/cli-command/
- https://docs.docker.com/docker-for-mac/release-notes/#docker-desktop-321

### 【補足】depends_onの使い所

他の解説記事で登場する depends_on のご紹介です。

`depends_on` はサービス間の依存関係を表現します。
簡単な使い方の例をご紹介します。

```yml:docker-compose.yml
version: "3.9"
services:
  web:
    build: .
    depends_on:
      - db
      - redis
  redis:
    image: redis
  db:
    image: postgres
```

- `docker compose up` 依存関係の順序でサービスを開始します。
  - この例では、`db` と `redis` のサービス開始後に `web` がサービス開始します。
- `docker compose stop` 依存関係の順序でサービスを停止します。
  - この例では、`web` のサービス停止後に `db`, `redis` がサービス停止します。
- 注意として、サービスの起動、停止順序を制御するだけで稼働順を制御するわけではないです。
  - 起動: 動き始めること(例: PC の電源ボタンを押すこと)
  - 稼働: 働きはじめること(例: PC が利用可能になること)

- https://docs.docker.com/compose/compose-file/compose-file-v3/#depends_on
- https://kotaroooo0-dev.hatenablog.com/entry/2020/07/25/000000

管理するサービスが増えて来た時に、どのサービスがどのサービスに依存してるのか明示して分かりやすくするために導入するのが良いと思います。
ハンズオンで取り入れると説明がめんどくさいので省略しています。(depends_on は実務でも特に使用してません)

### 【補足】linksは非推奨

`links` は コンテナ間の通信をするための設定です。
また、 `depends_on` と同じくサービスの起動順序を決定します。

`links` は古い記述方法で現在は非推奨となっています。

```
web:
  links:
    - "db"
    - "db:database"
    - "redis"
```

- https://docs.docker.com/compose/compose-file/compose-file-v3/#links

### 【補足】Composer公式のインストール方法は非推奨

https://getcomposer.org/download

Composer公式のインストール方法をそのまま利用すると下記のようになります。

```
FROM php:8.1-fpm-buster

RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" \
  && php -r "if (hash_file('sha384', 'composer-setup.php') === '906a84df04cea2aa72f40b5f787e49f22d4c2f19492ac310e8cba5b96ac8b64115ac402c8cd292b8a03482574915d1a8') { echo 'Installer verified'; } else { echo 'Installer corrupt'; unlink('composer-setup.php'); } echo PHP_EOL;" \
  && php composer-setup.php \
  && php -r "unlink('composer-setup.php');" \
  && mv composer.phar /usr/local/bin/composer
```

問題となるのが4行目のハッシュ値チェックの箇所です。
常に最新版を取得するので、インストーラが更新されるたびにハッシュ値の修正が必要になります。

常に最新版を取得したい時は下記の指定でokです。

```
COPY --from=composer /usr/bin/composer /usr/bin/composer
```

それでも勝手にバージョンが上がると困るので下記のようにマイナーバージョンまで指定してバージョン止めするのがベターです。

```
COPY --from=composer:2.2 /usr/bin/composer /usr/bin/composer
```

### 【補足】ADD命令の使い所

`COPY` 命令と似た命令で `ADD` 命令があります。
`ADD` 命令の特徴として、下記の内容があります。

- リモートからもファイル追加ができる。
- 圧縮ファイルが自動解凍される。

https://docs.docker.com/engine/reference/builder/#add
https://docs.docker.com/engine/reference/builder/#copy

`COPY` に比べて `ADD` は複雑な挙動になっています。
利用者が知らずに使うとセキュリティリスクが生じてしまいます。

`ADD` 命令を使う場合は注意して利用してください。

https://qiita.com/momotaro98/items/bf34eef176cc2bdb6892

### 【補足】MySQL8.0の認証方式

MySQL8.0.4以降はデフォルトのログイン認証方式が `mysql_native_password` から `caching_sha2_password` に変更されています。

PHP 7.1.16, 7.2.4 以降は `caching_sha2_password` に対応しています。

- https://www.php.net/manual/ja/ref.pdo-mysql.php
- https://qiita.com/ucan-lab/items/3ae911b7e13287a5b917

古い記事だと `mysql_native_password` の認証方式に戻す手順の名残があるので注意してください。

### 【補足】Sequel ProでMySQL8.0に接続できない

Sequel ProはMySQL8.0に対応してません。

- https://github.com/sequelpro/sequelpro/issues/2699
  - 5年以上Issueが残っています。

Sequel Aceという後継アプリが登場しているのでこちらを使いましょう。

- https://sequel-ace.com
- https://qiita.com/ucan-lab/items/b1304eee2157dbef7774

### 【補足】コンテナOSのalpineは上級者向け

DockerHub公式のphpイメージのコンテナOSとしては buster, stretch, alpine の3種類が用意されています。

各Dockerイメージの容量は下記の通りでした。

- buster (Debian 10) (405MB)
- stretch (Debian 9) (493MB)
- alpine (Alpine Linux) (83.5MB)

容量が小さいとデプロイやテストが高速化したりメリットはあります。
しかし、タイムゾーンを設定するだけでも一手間かかったり保守性が著しく下がります。

メリットよりデメリットの方が大きいので、よほどの理由がない限りコンテナOSはDebianもしくはUbuntuを利用しましょう。

### 【補足】container_nameは指定しない

コンテナ名ですが、重複したコンテナ名を複数立ち上げることができません。
下記のように `container_name` を明示的に指定して重複を避ける方法があります。

```yml:docker-compose.yml
version: "3.9"
services:
  app: # ここはサービス名(コンテナ名ではない)
    image: php:8.1-fpm-buster
    container_name: myapp
```

`container_name` は被らない名前を付ける必要があるので、管理するコンテナが増えるほど名付けがめんどくさくなります。

そもそも、Docker Composeで立ち上げたコンテナは、 `COMPOSE_PROJECT_NAME 変数 + _ + サービス名 + _ + 連番` とコンテナ名が自動的に命名されます。
`COMPOSE_PROJECT_NAME` に入るデフォルト値は `docker-compose.yml` があるディレクトリ名になります。

そのため他の階層で同じディレクトリ名でDocker Composeを使って立ち上げない限り被ることはありません。
その場合も `.env` ファイルで `COMPOSE_PROJECT_NAME` の環境変数を設定してあげればokです。

- https://qiita.com/okashoi/items/1ddf3724ad5c166e417b

### 【補足】Node.js のコンテナは作らない

本番デプロイ用にNode.js用のコンテナを用意するのはアリだと思います。
しかし、ローカル環境ではディスクアクセスが多すぎるため開発効率が著しく下がります。

現時点ではローカル用のためにNode.jsコンテナは用意しない方が良いと思われます。
各PCにNode.jsをインストールしてもらって、バージョン固定化するためには `.node-version` や `packages.json` に `engines` を指定して対策しましょう。

### 【補足】RUN命令はワンライナーで実行する

Dockerfileで命令を実行するたびにイメージレイヤーが作成されます。
命令が増えれば増えるほどイメージレイヤーも増えていき、最終的なDockerイメージの容量が肥大化します。

これを防ぐために `&&` でコマンドを繋げてワンライナーで実行します。

```dockerfile:infra/php/Dockerfile
FROM php:8.1-fpm-buster

RUN apt-get update && \
  apt-get -y install --no-install-recommends git unzip libzip-dev libicu-dev libonig-dev && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/*
```

また、下記のように中間ファイルやキャッシュファイルを削除するRUN命令を書いてもイメージレイヤーが増えるのでむしろ容量は増えてしまいます。

```dockerfile:infra/php/Dockerfile
RUN apt-get clean
RUN rm -rf /var/lib/apt/lists/*
```

### 【補足】Dockerfileは変更頻度が低い順に書く

Dockerfileの上から順に命令を実行し、イメージレイヤーを生成します。
イメージビルド時にイメージレイヤーのキャッシュがあれば、新しくイメージレイヤーを使うのではなく再利用します。

更新頻度の高い設定ファイル系などはなるべく後の方に記述します。

### 【補足】実務で使う場合はさらなる考慮が必要

- 設定値はイメージに含めず、環境変数で実行時に外から注入
- データベースはコンテナを使わない(Amazon RDS等)
- ログファイルは書かずに、標準出力(Amazon CloudWatch等)
- Dockerイメージをコンテナレジストリに登録(Amazon ECR等)
- コンテナの実行場所、コンテナ管理(Amazon ECS等)
- サーバー環境変数の管理(Amazon Secrets Manage等)