普通のlaravelのdocker及びcomposeのプロジェクト構成のサンプルを用意した

laravelで普通にdocker及びcomposeの環境を用意したので、記事にまとめました。
もしかしたら他のweb application frameworkでやるときも参考になるかもしれません。
サンプルのリポジトリ https://github.com/miyamonz/laravel-docker-example


## laravelのdocker環境を調べる

### 調査
laravelのdocker環境を用意しようとすると、いくつかのプロジェクトが見つかりますが、以下のように不満がありました。

#### [Laradock](https://laradock.io/)
設定が膨大すぎる。[環境変数は、デプロイ間で異なるアプリケーションに関する設定を保持するのに使うべき](https://12factor.net/config)で、各種ライブラリ等のON,OFFで使いたくない。
利用する予定がないライブラリに関する設定がDockerfileに記述されていてほしくない、リポジトリに入っていてほしくない。

ただし、直ちに始めるという点では良いものだと思いました。自分でDockerfileを作る際にも記述の参考になるので、プロジェクト自体は必要なときに参照すると良いと思います。

#### [Vessel](https://vessel.shippingdocker.com/)
getting startedにあるように、これはcomposerでインストールしてサービスプロバイダとして登録しますが、
dockerの環境がlaravelのライフサイクルに依存するのがおかしいと思われるので却下しました。詳細は追いませんでした。


### 前例
すでにlaravelの開発環境をdockerで用意する解説はQiita上にもいくつかあります。

- [Laravelの開発環境をDockerを使って構築する
](https://qiita.com/ucan-lab/items/17c806973e69792ada99)
- [dockerでPHP7.3+Laravel環境を15分で作る~2019年版](https://qiita.com/tomoyamachi/items/991a0d149e9585cac15f)

大変参考になりました。筆者の方々に感謝いたします。

### 当記事のスコープ
以上を踏まえた上で今回の記事では、開発および本番用の設定を考慮したものを用意しました。
dockerfile等の通常の書き方については省略し（ご自身で調べて頂いたほうが正確ですし）、各トピックを個別に触れます。

フォルダ構成

```tree
.
├── docker-compose.override.yml
├── docker-compose.prod.yml
├── docker-compose.yml
├── laravel
│   ├── Dockerfile
│   ├── node.Dockerfile
│   └── (その他のlaravelのファイルやフォルダ)
├── mysql
│   ├── Dockerfile
│   └── my.cnf
└── nginx
    ├── Dockerfile
    └── default.conf

```

## docker-compose.ymlの書き方

httpサーバやデータベースなどは、可能な限り公式のイメージを使うのが簡単です。
laravelとnodeを省略して、nginxとdbのサービスのみの開発時のみを想定した例です。

```docker-compose.yml
version: '3.7'
services:
  nginx:
    image: nginx:alpine
    ports:
      - "$APP_PORT:80"
    volumes:
      - ./laravel/public:/var/www/public
      - ./nginx/default.conf:/etc/nginx/conf.d/default.conf
    depends_on:
      - app #php-fpm
  db:
    image: mysql:8
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: your_app
      MYSQL_USER: user
      MYSQL_PASSWORD: password
    volumes:
      - ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf
      - mysql:/var/lib/mysql
volumes:
  mysql:
    driver: local

```

今回はデータベースはmysqlとします。
開発時のデータベースのデータは名前付きボリュームに格納しています。
なお上は例であり、サンプルリポジトリではdbやnginxのdockerfileを用意しています。
次の段落にあるように、実際はさらにこの内容を分割します。

### 複数のdocker-compose.ymlで環境毎の設定を分ける

`docker-compose.yml`があれば、これがdocker-composeコマンド実行時に読み込まれるのはご存知だと思いますが、

- -f で明示的に指定しない場合、`docker-compose.yml`及び`docker-compose.override.yml`も読み込んで、設定がマージされる
- -f で明示的に指定した場合、明示的に指定したものだけが読み込まれる。複数指定可能

という挙動をします。 https://docs.docker.com/compose/extends/

なので、開発用のymlファイルと本番用のymlファイルは、以下のようにするとよいでしょう。
[![Image from Gyazo](https://i.gyazo.com/f93db14cdfba11708162a41a1b1fc090.png)](https://gyazo.com/f93db14cdfba11708162a41a1b1fc090)

- 共通部分を`docker-compose.yml`
- 開発時の設定を`docker-compose.override.yml`
- 本番用の設定を`docker-compose.prod.yml`


## マルチステージビルド
一つのDockerfileの中で、ステージという区切りを作り、ステージ間でファイルやバイナリを受け渡す仕組みのことです。
https://docs.docker.com/develop/develop-images/multistage-build/

これのおかげで、以下のようなことがやりやすくなりました。

- ビルド時だけに必要なライブラリを別ステージに分けて、実行環境から省くことでイメージサイズを減らす
- 開発用と本番用の共通部分をまとめたり、異なる部分を別ステージに分けて、一つのDockerfileで簡潔に記述する


以下はcomposerなどの依存をマルチステージビルドで扱う簡単な例です

```Dockerfile
# composerの依存物のインストールするステージ
FROM composer:1.9 AS composer
COPY composer.json composer.lock .
RUN composer install

# php-fpmのステージ
FROM php:7.3-fpm-alpine3.8 AS php-fpm
WORKDIR /var/www/
COPY . /var/www/

## 別ステージからvendorをコピー
COPY --from=composer /app/vendor/ /var/www/vendor/
```

他にも、jsやcssのビルドを別のステージに分けて、出力されたファイルをphpやnginxのイメージに持ってくることが考えられます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366387/bfa6ceda-fbaa-b90b-9ce3-e749b80f0368.png)


具体的には、マルチステージビルドの`COPY --from=`は、別ステージだけでなく別のdocker imageからもファイルのコピーが可能なので、ここで自分が作成したイメージを指定すれば良いです。
これのおかげで各々のサービスのコンテクストを分けておくことができ、全体のフォルダ構成もやりやすくなりました。
以下がnginxのDockerfileの例です。

```Dockerfile
FROM nginx:alpine

COPY default.conf /etc/nginx/conf.d/default.conf
# プロジェクトで作成したイメージからファイルを持ってくる
COPY --from=laravel-docker-example_app /var/www/public /var/www/public
```

注意： `COPY --from=`の中では、[ARG等の変数展開ができないようです](https://github.com/docker/for-mac/issues/2155)。プロジェクト名等をARGやENVから渡したいですが諦めて直書きしました。少なくとも私の環境(docker for Mac)では動かなかったので、原因はissue等は追っていません。

## 開発時のボリュームやマウントについて

開発時は変更が直ちにコンテナ内に反映されるべきです。これはvolumesとしてマウントして、可能な限り再ビルドしないで済ませます。
vendorやnode_modulesなどのフォルダをどう扱うかについては別記事でまとめました。→　[dockerで開発環境構築時にvendor,node_modulesなどのフォルダをどうやっていくか](https://qiita.com/miyamonz/items/e0b0f86c47b26019d284)

これらの設定は開発時のものなので、上で述べたように`docker-compose.override.yml`に書きます。


## 終わりに
現在私がまだプロダクション環境のリリースを経ていないので、実際のリリースのための必要な設定が不足しているかもしれません。
他にも自分はこうやっているよ！などがありましたらコメントいただけると嬉しいです。