最強のLaravel開発環境をDockerを使って構築する


※Linuxはもちろんですが、Docker for Mac(M1 Mac含む), Docker for Windows で動作します。

## Dockerの勉強をしたい方向け

ハンズオン形式の記事を書いてるので、DockerでLaravel環境する方法を一から勉強したい人はこちらの記事がおすすめです。

[【初心者向け】20分でLaravel開発環境を爆速構築するDockerハンズオン](https://qiita.com/ucan-lab/items/56c9dc3cf2e6762672f4)

## 概要

Docker, Docker Compose を使って、nginx, php-fpm, MySQLの実行環境(LEMP)を構築して、最強のLaravel開発環境を構築する記事です。

Laravelが最低限動作して、シンプルで軽量かつベストプラクティスなコンテナ、ディレクトリ構成を考えました。
最小限の構成なので、Dockerイメージのビルドは[Laradock](https://laradock.io)より10倍以上高速にビルド完了します。

## リポジトリ

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

スターをもらえると嬉しいです☺️
※記事もなるべくメンテしていきますが、リポジトリに書かれていることが最新です。

## 対象読者

- Laravel中級者の方
- PHP, Linux, Git, Docker, Docker Compose, Composer, Nginx, MySQLの基礎知識を習得している方

## 前提

- Git
  - https://git-scm.com
  - [Mac GitHub SSH接続設定](https://qiita.com/ucan-lab/items/e02f2d3a35f266631f24)
- Docker for Mac / Windows
  - https://www.docker.com/products/docker-desktop
  - v3.4.0 以上

入れておいてね

```
[mac] $ git --version
git version 2.31.1
[mac] $ docker --version
Docker version 20.10.10, build b485636
[mac] $ docker compose version    
Docker Compose version v2.1.1
```

※ Docker Composeのバージョン
Compose V2がインストールされてない場合は、公式に沿ってインストール方法をご確認ください。
https://docs.docker.com/compose/cli-command/#installing-compose-v2

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

- https://matsuand.github.io/docs.docker.jp.onthefly/compose/cli-command-compatibility

`docker-compose` コマンドが `docker compose` へと `docker` のサブコマンドとして取り込まれることになりました。
本記事もそれに合わせて `docker compose` コマンドに表記を変更します。

### Docker Content Trust（DCT）を有効にする

`~/.bashrc` や `~/.zshrc` に追記する。

```bash:~/.bashrc
export DOCKER_CONTENT_TRUST=1
```

DCTは、Dockerイメージを「なりすまし」と「改ざん」から保護するセキュリティ機能です。

- Docker イメージへ発行者のデジタル署名を付ける
- イメージの利用時(pull など)に「発行者」と「イメージが改ざんされていないこと」を検証する

push, build, create, pull, run のコマンド実行時に自動で機能します。

- [Docker 1.8 リリースのまとめ](https://qiita.com/Arturias/items/ee1691e38fd1d912fff8#docker-content-trustdct)
- [Client Enforcement with Docker Content Trust](https://docs.docker.com/engine/security/trust/content_trust/#client-enforcement-with-docker-content-trust)

## コンテナ構成

下記の3つのコンテナで構成します。
LEMP環境を作成します。

```
├── app
├── web
└── db
```

よく利用されるシンプルな構成だと思うのでこの構成にしました。
プロジェクトに合わせてご自由にカスタムしてください👍

### app コンテナ

- アプリケーションサーバーのコンテナ
- PHPのバージョンは8.0系を利用する
- [Laravel 8.x サーバ要件](https://readouble.com/laravel/8.x/ja/deployment.html#server-requirements) を満たす
  - PHP7.3以上
  - BCMath PHP 拡張
  - Ctype PHP 拡張
  - Fileinfo PHP 拡張
  - JSON PHP 拡張
  - Mbstring PHP 拡張
  - OpenSSL PHP 拡張
  - PDO PHP 拡張
  - Tokenizer PHP 拡張
  - XML PHP 拡張
- [php](https://hub.docker.com/_/php), [composer](https://hub.docker.com/_/composer) のベースイメージを利用

### web コンテナ

- ウェブサーバーのコンテナ
- HTTPリクエストを受けて、HTTPレスポンスを返す
- phpファイルへのアクセスはappコンテナに投げる
- [nginx](https://hub.docker.com/_/nginx) のベースイメージを利用

### db コンテナ

- データベースサーバーのコンテナ
- MySQLのバージョンは8.0系を利用する
  - 5.7系は2020年10月にサポートが切れるので注意
  - 本番環境でAmazon Auroraを使用する場合は現時点で5.7互換までしかサポートしていないので注意
- [mysql/mysql-server](https://hub.docker.com/r/mysql/mysql-server) のベースイメージを利用
  - Docker Hub公式イメージではなく、OracleのMySQLチームがメンテしているDockerイメージを利用します

## ディレクトリ構成

```
.
├── src # Laravelプロジェクトのルートディレクトリ
├── infra
│     └── docker
│          ├── mysql
│          │   ├── Dockerfile
│          │   └── my.cnf
│          ├── nginx
│          │   ├── Dockerfile
│          │   └── default.conf
│          └── php
│              ├── Dockerfile
│              ├── php-fpm.d
│              │   └── zzz-www.conf => unixドメインソケットの設定ファイル
│              └── php.ini
├── Makefile
└── docker-compose.yml
```

## カスタマイズしてご利用したい方へ

[docker-laravel](https://github.com/ucan-lab/docker-laravel)リポジトリは、テンプレートリポジトリとして設定しています。

テンプレートリポジトリから作成した場合は、コミット履歴を継承されず綺麗な状態で開発を始められます！

<img width="971" alt="ucan-lab_docker-laravel__Build_laravel_development_environment_with_docker-compose_.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/52879/908f5012-a478-b88d-c67c-58ce5e3d499e.png">

カスタマイズして使う場合は、後述の`git clone`等する際はご自身のリポジトリをご指定してご利用ください。

- [GitHubのTemplate Repository機能のすゝめ](https://qiita.com/ryo2132/items/08f0561804c798012146)

## 使い方

`composer create-project` で新規作成したい場合と `composer install` で環境構築したい場合がありますので、それぞれ説明します。
[Makefile](https://github.com/ucan-lab/docker-laravel/blob/master/Makefile) を見てもらえると何やってるか分かりやすいです。

**注意**

`web`コンテナは`80`番ポート、`db`コンテナは`3306`ポートを公開しています。

既にポートが使用されている場合は、`docker-compose.yml`の`ports`の指定を変更してご利用ください。

### A. Laravelプロジェクトの新規作成

```
[mac] $ git clone git@github.com:ucan-lab/docker-laravel.git
[mac] $ cd docker-laravel
[mac] $ mkdir -p src
[mac] $ docker compose build
[mac] $ docker compose up -d
[mac] $ docker compose exec app composer create-project --prefer-dist laravel/laravel .
[mac] $ docker compose exec app php artisan key:generate
[mac] $ docker compose exec app php artisan storage:link
[mac] $ docker compose exec app chmod -R 777 storage bootstrap/cache
[mac] $ docker compose exec app php artisan migrate
```

http://localhost

※最新のLaravelバージョンがインストールされます。

### A'. Laravelプロジェクトをバージョン指定して新規作成

古いバージョンを指定したい場合の手順です。

```
[mac] $ git clone git@github.com:ucan-lab/docker-laravel.git
[mac] $ cd docker-laravel
[mac] $ mkdir -p src
[mac] $ docker compose build
[mac] $ docker compose up -d

[mac] $ docker compose exec app composer create-project --prefer-dist "laravel/laravel=6.*" .

[mac] $ docker compose exec app php artisan key:generate
[mac] $ docker compose exec app php artisan storage:link
[mac] $ docker compose exec app chmod -R 777 storage bootstrap/cache
[mac] $ docker compose exec app php artisan migrate
```

http://localhost

`composer create-project --prefer-dist "laravel/laravel=6.*" .` インストールしたいLaravelバージョンを指定してます。

### B. 既存のLaravelプロジェクトの環境を構築する

`docker-laravel/src` ディレクトリにLaravelプロジェクトを配置します。

```
[mac] $ git clone git@github.com:ucan-lab/docker-laravel.git

# Laravelプロジェクトを docker-laravel/src へクローンする
[mac] $ git clone git@github.com:laravel/laravel.git docker-laravel/src

[mac] $ cd docker-laravel
[mac] $ make init

[mac] $ docker compose build
[mac] $ docker compose up -d
[mac] $ docker compose exec app composer install
[mac] $ docker compose exec app cp .env.example .env
[mac] $ docker compose exec app php artisan key:generate
[mac] $ docker compose exec app php artisan storage:link
[mac] $ docker compose exec app chmod -R 777 storage bootstrap/cache
[mac] $ docker compose exec app php artisan migrate:fresh
```

http://localhost

これで既存のLaravelプロジェクトの環境構築は完了です。

## 補足

この構成だと同一リポジトリでLaravelのソースコードも管理することになります。
インフラ部分とLaravel部分とGitリポジトリを分けたい気持ちもあるかと思います。
ケースバイケースだとは思いますが、個人的には小規模な開発であればまとめてしまっていいんじゃないかなと思ってます。

## Tips

軽く使い方をご紹介します。

### 初期設定

- [Laravel 初期設定](https://qiita.com/ucan-lab/items/8eab84e37421f907dea0)

Laravelの初期設定に関することを別記事としてまとめました。

### Makeコマンド、Makefileについて

`docker compose` コマンドは入力するには長く、プロジェクト毎にエイリアス設定するのはとても面倒です。
`Makefile` を用意することで、短いコマンドでコマンドが実行できるようになります。

用意している `make` コマンドと元の`docker compose`コマンドを併記してご紹介します。

### 基本

```shell-session
# コンテナを作成する
[mac] $ docker compose up -d

# コンテナを破棄する
[mac] $ docker compose down

# コンテナ、イメージ、ボリュームを破棄する
[mac] $ docker compose down --rmi all --volumes

# コンテナ、ボリュームを破棄する
[mac] $ docker compose down --volumes
```

- コンテナを破棄するとコンテナ内のデータは破棄されます。
- イメージを破棄するとDockerイメージのビルドが必要です。
- ボリュームを破棄すると名前付きボリュームで管理しているデータベースの中身が消えます。

Docker Composeコマンドは長ったらしいのでMakefileも用意してます。ご自由に変更してお使いください。

https://github.com/ucan-lab/docker-laravel/blob/main/Makefile

### Laravelプロジェクトの初期設定

Laravelプロジェクトの初期設定について記事にまとめました。
よければご参考ください。

https://qiita.com/ucan-lab/items/8eab84e37421f907dea0

### appコンテナに入る

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

### webコンテナに入る

```
[mac] $ docker compose exec app ash
```

### dbコンテナに入る

```
[mac] $ docker compose exec db bash
```

### dbコンテナのMySQLに接続する

```shell-session
[mac] $ docker compose exec db bash -c 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE'
```

### Laravelのマイグレーションを実行する

```shell-session
# migrate
[mac] $ docker compose exec app php artisan migrate

# all drop table & migrate & seeding
[mac] $ docker compose exec app php artisan migrate:fresh --seed

# seeding
[mac] $ docker compose exec app php artisan db:seed
```

### テストの実行

```shell-session
[mac] $ docker compose exec app php artisan test
```

<!-- 古くなったのでコメントアウト

## 解説

以降の内容は記事の解説になります。
過不足等ありましたら、コメントにて質問・補足等いただけるとありがたいです🙏

また、最新のリポジトリと若干の差異がある場合がありますのでご了承ください🙏
(なるべくは同期取りたいと思っています。)

### docker-compose.yml

```yaml:docker-compose.yml
version: "3.9"
volumes:
  php-fpm-socket:
  db-store:
services:
  app:
    build:
      context: .
      dockerfile: ./infra/docker/php/Dockerfile
    volumes:
      - type: volume
        source: php-fpm-socket
        target: /var/run/php-fpm
        volume:
          nocopy: true
      - type: bind
        source: ./src
        target: /work/src
    environment:
      - DB_CONNECTION=mysql
      - DB_HOST=db
      - DB_PORT=3306
      - DB_DATABASE=${DB_NAME:-laravel_local}
      - DB_USERNAME=${DB_USER:-phper}
      - DB_PASSWORD=${DB_PASS:-secret}

  web:
    build:
      context: .
      dockerfile: ./infra/docker/nginx/Dockerfile
    ports:
      - target: 80
        published: ${WEB_PORT:-80}
        protocol: tcp
        mode: host
    volumes:
      - type: volume
        source: php-fpm-socket
        target: /var/run/php-fpm
        volume:
          nocopy: true
      - type: bind
        source: ./src
        target: /work/src

  db:
    build:
      context: .
      dockerfile: ./infra/docker/mysql/Dockerfile
    ports:
      - target: 3306
        published: ${DB_PORT:-3306}
        protocol: tcp
        mode: host
    volumes:
      - type: volume
        source: db-store
        target: /var/lib/mysql
        volume:
          nocopy: true
    environment:
      - MYSQL_DATABASE=${DB_NAME:-laravel_local}
      - MYSQL_USER=${DB_USER:-phper}
      - MYSQL_PASSWORD=${DB_PASS:-secret}
      - MYSQL_ROOT_PASSWORD=${DB_PASS:-secret}
```

`app`, `web`, `db` の3つのコンテナを定義しています。
ミニマムな構成を考えれば `app` と `db` コンテナだけあれば十分ですが、本番に近い構成と利便性を考慮してこの構成にしています。

#### version

https://docs.docker.com/compose/compose-file

```
version: "3.9"
```

`Docker Compose` のバージョンを指定します。

#### volumes

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

```
volumes:
  php-fpm-socket:
  db-store:
```

名前付きボリューム(トップレベルボリューム)を定義しています。

unixソケット(php-fpm-socket)のボリュームは `app` コンテナと `web` コンテナで共用したいのでマウントしてます。
データベース(db-store)のデータはコンテナを破棄しても残しておきたいのでボリュームとして定義しています。

#### services.app

https://docs.docker.com/compose/compose-file/#build

```
  app:
    build:
      context: .
      dockerfile: ./infra/docker/php/Dockerfile
    volumes:
      - type: volume
        source: php-fpm-socket
        target: /var/run/php-fpm
        volume:
          nocopy: true
      - type: bind
        source: ./src
        target: /work/src
    environment:
      - DB_CONNECTION=mysql
      - DB_HOST=db
      - DB_PORT=3306
      - DB_DATABASE=${DB_NAME:-laravel_local}
      - DB_USERNAME=${DB_USER:-phper}
      - DB_PASSWORD=${DB_PASS:-secret}
```

`app`(アプリケーションサーバ)コンテナの定義です。PHPを実行します。
`build` で `context` と `dockerfile` 明示して指定してます。Dockerfileの詳細は後述します。

- [docker-compose.ymlのbuild設定はとりあえずcontextもdockerfileも埋めとけって話](https://qiita.com/sam8helloworld/items/e7fffa9afc82aea68a7a)

`volumes` で名前付きボリュームとホストパスをコンテナにマウントしてます。
`../src:/work/src` Laravelのソースコードをマウントしてます。
他はunixソケットで使うものをマウントしてます。
`environment` でサーバー環境変数を設定してます。

##### Laravelの環境変数について

Laravelの.envを触ることなく動作させたかっただけなので、実際の開発で使うときは削除して良いと思います。

Laravelの `.env` の内容を書き換えるという手順を省くためサーバー環境変数を設定しています。
ちなみにLaravelの `.env` よりも<font color="Red">**サーバー環境変数の値の方が優先**</font>されます。

- `${VARIABLE:-sample}` は `$VARIABLE` 未設定の場合、`sample` という値が設定されます。
- https://docs.docker.com/compose/compose-file/compose-file-v3/#variable-substitution

##### `DB_HOST=db` について

`DB_HOST` はデータベースのホストを指します。
未指定の場合は `127.0.0.1` と設定されているので `app` コンテナを見に行ってしまいます。
そのため、 `db` とサービス名を指定しています。

※サービス名(`db`) と コンテナ名(`docker-laravel_db_1`) は意味が異なるのでご注意ください。

#### services.web

https://docs.docker.com/compose/compose-file/#ports

```
  web:
    build:
      context: .
      dockerfile: ./infra/docker/nginx/Dockerfile
    ports:
      - target: 80
        published: ${WEB_PORT:-80}
        protocol: tcp
        mode: host
    volumes:
      - type: volume
        source: php-fpm-socket
        target: /var/run/php-fpm
        volume:
          nocopy: true
      - type: bind
        source: ./src
        target: /work/src
```

`web`(ウェブサーバ)コンテナの定義です。PHP以外の静的コンテンツを返却します。
`ports` ポートを公開します。(HOST:CONTAINER)
nginxのデフォルトのポート番号は 80 番です。

#### services.db

```
  db:
    build:
      context: .
      dockerfile: ./infra/docker/mysql/Dockerfile
    ports:
      - target: 3306
        published: ${DB_PORT:-3306}
        protocol: tcp
        mode: host
    volumes:
      - type: volume
        source: db-store
        target: /var/lib/mysql
        volume:
          nocopy: true
    environment:
      - MYSQL_DATABASE=${DB_NAME:-laravel_local}
      - MYSQL_USER=${DB_USER:-phper}
      - MYSQL_PASSWORD=${DB_PASS:-secret}
      - MYSQL_ROOT_PASSWORD=${DB_PASS:-secret}
```

`db`(データベース)コンテナの定義です。
MySQLのデフォルトのポート番号は 3306 番です。

### docker/php/Dockerfile

https://docs.docker.com/engine/reference/builder

```Dockerfile
FROM php:8.0-fpm-buster
LABEL maintainer="ucan-lab <yes@u-can.pro>"
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]

# timezone environment
ENV TZ=UTC \
  # locale
  LANG=en_US.UTF-8 \
  LANGUAGE=en_US:en \
  LC_ALL=en_US.UTF-8 \
  # composer environment
  COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer

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

RUN apt-get update && \
  apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  locale-gen en_US.UTF-8 && \
  localedef -f UTF-8 -i en_US en_US.UTF-8 && \
  mkdir /var/run/php-fpm && \
  docker-php-ext-install intl pdo_mysql zip bcmath && \
  composer config -g process-timeout 3600 && \
  composer config -g repos.packagist composer https://packagist.org

COPY ./infra/docker/php/php-fpm.d/zzz-www.conf /usr/local/etc/php-fpm.d/zzz-www.conf
COPY ./infra/docker/php/php.ini /usr/local/etc/php/php.ini

WORKDIR /work/src
```

#### FROM 命令

https://docs.docker.com/engine/reference/builder/#from

ベースイメージを指定します。(IMAGE:TAG)
PHPはDocker Hubに公式イメージが用意されています。
よほど特殊な環境を構築したい場合を除いては公式イメージをベースイメージとするのが良いでしょう。

https://hub.docker.com/_/php

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

PHPイメージの指定は `<image>:<verion>-<variant>-<os>` こんな風にタグ付けされていきます。

##### PHP の version

PHPのタグバージョンは `8.0` のマイナーバージョンまで指定するのがベターです。
パッチバージョンは後方互換性が保証されるので、なるべく最新を当てておきたいです。

PHPのバージョンのリリース頻度ですが、マイナーバージョンのアップデートは年に1回(11〜12月頃)行われています。
パッチバージョンのリリースは月1程度です。このPHPのサポート期間としては初回リリースから3年間です。
執筆時点でLaravel8が対応しているPHPのバージョンは `7.3` 以降となっています。
特に理由がなければ新しいバージョンを採用してください。

- [バージョンごとの一覧表](https://qiita.com/Web_akira/items/c3e5dbd86ab82affe8fa#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E3%81%94%E3%81%A8%E3%81%AE%E4%B8%80%E8%A6%A7%E8%A1%A8)

##### PHP の variant

PHPのイメージタグの種類として `cli`, `apache`, `fpm`, `zts` の4種類が用意されています。

- cli（Apacheを経由せず、直接コマンドライン上で実行）
  - ウェブサーバ用途ではない
  - 使い捨てコンテナ
  - 他のイメージを構築するためのベースイメージ
- apache
  - ウェブサーバ(Apache mpm_prefork) ＋ アプリケーションサーバ(Apache mod_php)の構成
  - PHPと組み合わせたDebianのApache httpdが含まれている
- fpm（FastCGI Process Manager）
  - PHPを実行するアプリケーションサーバのみの構成
  - [fpmとは](http://php.net/manual/ja/install.fpm.php)
  - [cgiとは](https://ja.wikipedia.org/wiki/Common_Gateway_Interface)
- zts（phpがZend Thread Safetyでビルドされている）
  - マルチスレッド環境に対応するためのPHPの機構です
  - PHPはシングルスレッド環境(NTS)で動かす方が一般的です。

今回はnginxを使ってるので`fpm`を選択します。

##### PHP の コンテナOS

コンテナOSとしては `buster`, `stretch`, `alpine` の3種類が用意されています。

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

alpineイメージサイズの小ささには驚くのですが、
タイムゾーンの設定するのにも手間がかかったりメンテナンスも面倒で初心者にオススメはできないという考えに至り `buster` を選択しています。

他のコンテナOSを使いたい場合は自作しましょう...。

#### SHELL 命令

https://docs.docker.com/engine/reference/builder/#shell

```
SHELL ["/bin/bash", "-oeux", "pipefail", "-c"]
```

コマンドのシェル形式に使用されるデフォルトのシェルをオーバーライドできます。
Linuxのデフォルトのシェルは `["/bin/sh", "-c"]`

おまじないです。。

`RUN` 命令でコマンドをワンライナーにしているのですが、
それだと失敗した際にどこで落ちたのか分かりづらいためオプションを付けています。

#### ARG 命令

https://docs.docker.com/engine/reference/builder/#arg
Dockerのビルド時に引数として変数を定義できます。

#### ENV 命令

https://docs.docker.com/engine/reference/builder/#env

`ENV` 命令は環境変数を設定します。
`ARG` 命令と違ってこちらはコンテナ起動時にもこの値は使用できます。

```
# timezone environment
ENV TZ=UTC \
  # locale
  LANG=en_US.UTF-8 \
  LANGUAGE=en_US:en \
  LC_ALL=en_US.UTF-8 \
  # composer environment
  COMPOSER_ALLOW_SUPERUSER=1 \
  COMPOSER_HOME=/composer
```

`\` をつなげてワンライナーにしています。
こうすることでイメージレイヤー数を減らすことができます。

やってることは、タイムゾーン、言語設定、Composerの設定です。

#### COPY 命令(マルチステージビルド)

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

新しいファイルまたはディレクトリをコンテナのパスにコピーします。

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

`--from` オプションを付けると別のイメージのファイルを指定できます。(マルチステージビルド)
`composer` のインストールがとてもシンプルになっています。

もうすぐComposer Version 2がリリースされます。
いきなり動かなくなってしまうと困るためComposerのバージョンを1系で固定化しました。

追記: 2020年10月24日にComposerバージョン2がリリースされました🎉🎉🎉 に伴って環境をアップグレードしました。

- https://php.watch/articles/composer-2
- https://blog.packagist.com/composer-2-0-is-now-available/
- https://github.com/ucan-lab/docker-laravel/pull/72

#### RUN 命令

https://docs.docker.com/engine/reference/builder/#run

`RUN` 命令は現在のイメージの上に任意のコマンドを実行した結果をコミットします。
ワンライナーで実行することによって、イメージレイヤ数を減らすことができます。

```
RUN apt-get update && \
  apt-get -y install git libicu-dev libonig-dev libzip-dev unzip locales && \
  apt-get clean && \
  rm -rf /var/lib/apt/lists/* && \
  locale-gen en_US.UTF-8 && \
  localedef -f UTF-8 -i en_US en_US.UTF-8 && \
  mkdir /var/run/php-fpm && \
  docker-php-ext-install intl pdo_mysql zip bcmath && \
  composer config -g process-timeout 3600 && \
  composer config -g repos.packagist composer https://packagist.org
```

- Laravelの実行に必要なライブラリのインストール
- OSの言語設定
- PHP拡張ライブラリのインストール
  - [Laravel サーバ要件](https://readouble.com/laravel/7.x/ja/installation.html#server-requirements) を満たすため

を行ってます。

#### COPY 命令

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

新しいファイルまたはディレクトリをコンテナのパスにコピーします。

```
COPY ./infra/docker/php/php-fpm.d/zzz-www.conf /usr/local/etc/php-fpm.d/zzz-www.conf
COPY ./infra/docker/php/php.ini /usr/local/etc/php/php.ini
```

ホスト側に置いているPHPの設定ファイルのコピーをしているだけです。
PHPの設定ファイルも少し解説します。

```ini:./docker/php/php-fpm.d/zzz-www.conf
[www]
listen = /var/run/php-fpm/php-fpm.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0666
access.log = /proc/self/fd/1
```

nginxとphp-fpm間の通信ですが、`TCP`か`UNIXドメインソケット`で通信します。
`UNIXドメインソケット`の方が遥かにスループットが優れているためこちらを採用しました。
また、php-fpmのアクセスログの出力を抑制しています。

- [Docker + Nginx + Laravel(PHP-FPM)な環境のログを整理する](https://zenn.dev/nagi125/articles/8f6d4639342f1fc644eb)

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

[Date]
date.timezone = ${TZ}

[mysqlnd]
mysqlnd.collect_memory_statistics = on

[Assertion]
zend.assertions = 1

[mbstring]
mbstring.language = Japanese
```

php.iniの各設定に関しては別記事にまとめていますので、そちらをご参照ください。

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

### docker/nginx/Dockerfile

```Dockerfile:./docker/nginx/Dockerfile
FROM node:16-alpine as node
FROM nginx:1.20-alpine
LABEL maintainer="ucan-lab <yes@u-can.pro>"
SHELL ["/bin/ash", "-oeux", "pipefail", "-c"]

ENV TZ=UTC

RUN apk update && \
  apk add --update --no-cache --virtual=.build-dependencies g++

# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt
# nginx config file
COPY ./infra/docker/nginx/*.conf /etc/nginx/conf.d/

WORKDIR /work/src
```

ベースイメージとしてDocker Hub公式イメージを使用します。

#### FROM 命令 node

- https://hub.docker.com/_/node/

#### FROM 命令 nginx

- https://hub.docker.com/_/nginx

#### COPY 命令

`node` のベースイメージから `node` と `npm` と `yarn` をコピーしています。

```
# node command
COPY --from=node /usr/local/bin /usr/local/bin
# npm command
COPY --from=node /usr/local/lib /usr/local/lib
# yarn command
COPY --from=node /opt /opt
```

`/opt/yarn-v1.22.0` と yarn パッケージがインストールされています。
もし `npm`, `yarn` どちらか使用したくない場合は削除して使用してokです。

それからnginxの設定ファイルをコピーしています。

```./docker/nginx/default.conf
access_log /dev/stdout main;
error_log /dev/stderr warn;

server {
    listen 80;
    root /work/src/public;

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

    index index.html index.htm 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 unix:/var/run/php-fpm/php-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
    }

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

nginxの設定ファイルはLaravelの公式ドキュメントを参考にしています。
https://readouble.com/laravel/8.x/ja/deployment.html#nginx

`root` でドキュメントルートを設定しているのと、
`fastcgi_pass` でunixドメインソケットを指定しています。

### docker/mysql/Dockerfile

```
FROM mysql/mysql-server:8.0
LABEL maintainer="ucan-lab <yes@u-can.pro>"

ENV TZ=UTC

COPY ./infra/docker/mysql/my.cnf /etc/my.cnf
```

ベースイメージとしてDocker Hub公式イメージを使用します。

https://hub.docker.com/_/mysql

環境変数と設定ファイルのコピーだけ行っています。

#### MySQL8.0

PHP 7.1.16, 7.2.4 以降のバージョンから新しい認証方式 `caching_sha2_password` にも対応するようなりましたのでMySQL8.0系の導入敷居は下がったかなと思います。

#### my.cnf

`my.cnf` の説明です。

```./docker/mysql/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_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
```

MySQL8.0からデフォルトの文字コードは `utf8mb4` 文字列照合順序は `utf8mb4_0900_ai_ci` となっています。
日本語を扱う場合はMySQL8.0.1で新しく導入された日本語用の照合順序 `utf8mb4_ja_0900_as_cs_ks` を設定することを推奨します。

https://tmtms.hatenablog.com/entry/201805/mysql-innovation-day-tokyo

- `utf8mb4_ja_0900_as_cs_ks`
  - `utf8mb4` utf8で、マルチバイトを4バイトとする文字コードになります。
  - `ja` 日本語用
  - `0900` Unicodeのバージョン 9.00
- `as` Accent Sensitiveの略称。アクセント違いは異なる文字
  - 「は」と「ぱ」は異なる文字として評価される。
- `cs` Case Sensitiveの略称。大文字小文字は異なる文字
  - 「あ」と「ぁ」は異なる文字として評価される。
- `ks` Kana Sensitiveの略称。カタカナひらがなは異なる文字
  - 「あ」と「ア」は異なる文字として評価される。

-->

## 関連記事

- [Build laravel development environment with docker](https://dev.to/ucan_lab/build-laravel-development-environment-with-docker-1lbo)

### Docker × Laravel シリーズ記事

- [Laravel Advent Calendar 2020](https://qiita.com/advent-calendar/2020/laravel) の24日目の記事で [Laravel 初期設定](https://qiita.com/ucan-lab/items/8eab84e37421f907dea0) の記事を書きました
- [Dockerを使ってLaravelのローカル開発環境を作る(Apache版)](https://qiita.com/ucan-lab/items/38cd04cee1f3f9e024b9)
- [Docker x Laravel 脅威の1ファイル！最小構成でローカル開発環境を構築する](https://qiita.com/ucan-lab/items/11ab92b672e3fa2d9bdb)
- [【忙しい人向け】カップ麺より早く作るDockerでLaravel開発環境構築](https://qiita.com/ucan-lab/items/d7752b43a9e89cc16b22)
- [Docker × Laravel ローカル環境でHTTPS(SSL)通信する](https://qiita.com/ucan-lab/items/c7f0690227ce7da4a172)
- [Docker × Laravel × ngrok ローカル環境を一時的に外部へ公開する](https://qiita.com/ucan-lab/items/196cce2f1c78d7ae6f53)
- [Docker × Laravel Vueをインストールしてユーザー登録＆ログイン機能を実装する](https://qiita.com/ucan-lab/items/7a81b62b8b666e8de783)
  - Laravel7.x 系以前
- [Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する](https://qiita.com/ucan-lab/items/7824d1293fef4698c212)
  - Laravel8.x 系
- [Docker × Laravel メールの送信処理をローカルで確認する](https://qiita.com/ucan-lab/items/49e7f4986564f68e95c4)
- [Docker × Laravel テスト用のデータベースコンテナを構築する](https://qiita.com/ucan-lab/items/42c1814d8bd69895374c)
- [Docker × Laravel コードを自動整形するコンテナを構築する](https://qiita.com/ucan-lab/items/ab8c17d7144e8fc86551)
- [Docker x Laravel ブラウザ自動リロード(Hot Module Replacement)](https://qiita.com/ucan-lab/items/5e3653f32baca7b4ddd1)
- [Docker x Laravel phpMyAdmin のコンテナを構築する](https://qiita.com/ucan-lab/items/a0c8d6d73aca03325362)
- [Docker x Laravel めちゃくちゃ遅い Docker for Mac を爆速化する](https://qiita.com/ucan-lab/items/a88e2e5c2a79f2426163)

### Laravel シリーズ記事

- [Laravel × Dacapo データベースマイグレーションサポートツールの使い方](https://qiita.com/ucan-lab/items/e75ef2bba744d9dcd4d0)
- [Docker × Laravel × Dacapo × SchemaSpyでER図を自動生成する](https://qiita.com/ucan-lab/items/76316c2723bdcce6eee1)
- [Laravel5.8 文字列をキャメルケース・パスカルケース・スネークケース・ケバブケース・単数系・複数形に変換する](https://qiita.com/ucan-lab/items/350ad5f3bce1a9849613)
- [Laravel tinker データベースの接続できるか疎通確認する](https://qiita.com/ucan-lab/items/20a5a6ad7faea7cd622f)
- [Laravel SQLの実行クエリログを出力する](https://qiita.com/ucan-lab/items/753cb9d3e4ceeb245341)
- [Laravel7 makeコマンドのひな形ファイルをカスタマイズする](https://qiita.com/ucan-lab/items/eab7d869359b5fa72ba0)
- [Laravel7 make:xxx コマンドを作ってひな形ファイルを生成する](https://qiita.com/ucan-lab/items/387d964265225321f467)
- [composer update でメモリオーバーする場合の対策](https://qiita.com/ucan-lab/items/af39b71c6eb304ddf696)
- [【Laravel初期設定】開発を楽しくするパッケージ＆ツール](https://qiita.com/ucan-lab/items/9bed7aeb7d165bc26b2d)
- [Laravel 標準搭載のデバッグ機能 tinker コマンドのご紹介](https://qiita.com/ucan-lab/items/0e9537be6d5709e1d099)
- [Laravel 自作のヘルパ関数を追加する](https://qiita.com/ucan-lab/items/afae48ee5328b77b6c10)
- [Laravel 標準ヘルパ関数を自作ヘルパ関数でオーバーライドする](https://qiita.com/ucan-lab/items/8c291c6484a547db17a5)
- [PHP7.4 ぼくのかんがえたさいきょうのphp.ini](https://qiita.com/ucan-lab/items/0d74378e1b9ba81699a9)
- [Laravel5.8 EloquentやDBの差分を取得する](https://qiita.com/ucan-lab/items/ab2f284829fb14b987ac)
- [Laravel PHP Insightsを使ってコード品質を分析する](https://qiita.com/ucan-lab/items/53a7c7647d7fd4298950)
- [Laravel tinker で Faker を試す方法](https://qiita.com/ucan-lab/items/bc421788d4a352afc6ea)
- [Laravel開発時に便利なmake(Makefile)コマンド集](https://qiita.com/ucan-lab/items/cfe646a34f6d8c9728ed)
- [Laravel キャッシュクリア＆最適化](https://qiita.com/ucan-lab/items/c1e561d20cc591966c25)
- [Laravel 8 の新機能！マイグレーションスカッシングで肥大化したマイグレーションファイルを1つにまとめる](https://qiita.com/ucan-lab/items/2f00191dcf8371347d13)
- [Laravel 8 の新機能！時間テストヘルパー(タイムトラベル)を使ってみた！](https://qiita.com/ucan-lab/items/e983fec6897d85381c20)

### Docker Laravel 関連記事

- [docker-laravel の Docker 構成をちゃんと理解する【docker-compose.yml 編】](https://qiita.com/tkek321/items/9c0c5cec1dc349c67f5f#_reference-35524d290e6e8de2af7b)
  - 超細かく解説してくれてます。