docker-laravel の Docker 構成をちゃんと理解する【docker-compose.yml 編】


12/8 は @FrozenVoice さんの Notion の使い方の記事でした。

https://qiita.com/FrozenVoice/items/63786633721e4504d45d

自分も最近 Notion を使う機会が多いですが、いつも雰囲気で使っているので、かなりありがたい記事でした。
ありがとうございました。

## この記事について

Laravel の環境を構築する際にはいつも @ucan-lab さんの [docker-laravel](https://github.com/ucan-lab/docker-laravel) にお世話になっているのですが、コピペするばかりでちゃんと理解できていなかったので、これを機会に調べてみることにしました。

以前社内の勉強会で[記事](https://qiita.com/tkek321/items/47d68b6bf40835015006)を書いたのですが、そこからさらに掘り下げてみたいと思います。

この記事の目的は、あくまで自分で調べて理解することなので、

docker-laravel の解説が見たいって人はこの記事を参考にしてもらうのが一番いいと思います。

https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4

基本調べたことを書いていますが、自分の解釈が入っている部分もあります。
もし間違いがあれば指摘して欲しいです。

## docker-compose.yml

```yaml
version: "3.9" # ① docker-compose.yaml のバージョン
volumes: # ② ボリュームを生成
  php-fpm-socket:
  db-store:
  psysh-store:
services: # ③ サービスの定義
  app: # ④ サービスの名前
    build: # ⑤ ビルド時に適応される構成
      context: .
      dockerfile: ./infra/docker/php/Dockerfile # Dockerfile の中身は後で確認します
    volumes: # ⑥ バインドマウントやボリュームマウントなどの設定
      - type: volume # ⑦ unixソケットをマウント
        source: php-fpm-socket
        target: /var/run/php-fpm
        volume:
          nocopy: true
      - type: bind # ⑧ Laravel のソースコードをマウント
        source: ./backend
        target: /work/backend
      - type: volume # ⑨ psysh をマウント
        source: psysh-store
        target: /root/.config/psysh
        volume:
          nocopy: true
    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 # ⑫ unixソケットをマウント
        source: php-fpm-socket
        target: /var/run/php-fpm
        volume:
          nocopy: true
      - type: bind # ⑬  Laravel のソースコードをマウント
        source: ./backend
        target: /work/backend

  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 # ⑭ 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}

```

### ① docker-compose.yaml のバージョン

```
version: "3.9" # ① docker-compose.yaml のバージョン
```

- 指定されるバージョンによって docker-compose.yml の書き方が変わる

### ② ボリュームを生成

```
volumes: # ② ボリュームを生成
  php-fpm-socket:
  db-store:
  psysh-store:
```

https://matsuand.github.io/docs.docker.jp.onthefly/storage/volumes/

- Docker Engine 上で確保した領域にマウントすることでコンテナを削除してもデータが消えない
- 名前は任意につけて大丈夫

`docker volume create php-fpm-socket`
`docker volume create db-store`
`docker volume create psysh-store`

としているのと同じ

- ボリュームは Docker Engine 上で管理されるため、物理的な位置を意識する必要はない
- ボリュームはブラックボックスになっていて、Docker ホスト（自分のPC）からは変更することはできない


### ③ サービスの定義

```
services: # ③ サービスの定義
   サービス名:
     サービスの定義:
   サービス名:
     サービスの定義:
```

- サービス１つ１つがコンテナ

- 今回の場合だと `app` コンテナ、 `web` コンテナ、 `db` コンテナを定義している



### ④ サービスの名前

```
app: # ④ サービスの名前
```

[サービス設定リファレンス](https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-file-v3/#service-configuration-reference)

- 任意でつけて問題ない

### ⑤ ビルド時に適応される構成

```
build: # ⑤ ビルド時に適応される構成
      context: .
      dockerfile: ./infra/docker/php/Dockerfile # Dockerfile の中身は後で確認します
```

https://qiita.com/sam8helloworld/items/e7fffa9afc82aea68a7a

- `context` と `dockerfile` の位置を明示的に指定している

- `context` とは　`docker build` コマンドを実行したカレントディレクトリ

- Docker はカレントディレクトリ（コンテキスト）外のファイルにはアクセスできない

- `context` でコンテキストをルートディレクトリに指定することで、`dockerfile` からどのディレクトリへのアクセスも可能にしている

### ⑥ バインドマウントやボリュームマウントなどの設定

```
volumes: # ⑥ バインドマウントやボリュームマウントなどの設定
```

【バインドマウント】

- Docker ホストにあらかじめディレクトリを作っておいて、それをマウントする方法。

- ホストで変更した Laravel ファイルが Docker 内にも反映されるのはこいつのおかげ。

【ボリュームマウント】

- ホスト上のディレクトリではなくて、 Docker Engine 上で確保した領域をマウントする方法。

- 確保した場所のことを「データボリューム」、「ボリューム」と言ったりする。

- `volumes` で作成した。

https://amateur-engineer-blog.com/docer-compose-volumes/#toc5

- 一行で記述する方法と、複数行で記述する方法がある。今回は複数行で記述してある。

複数行での記述方法

> - type : マウントタイプ(volume, bind, tmpfs, npipe)
- source : マウント元
- target : マウントされるコンテナのパス
- read_only : 読み込み専用
- bind : バインドオプション
    - propagation : バインドの伝播モード
- volume : ボリュームオプション
    - nocopy : ボリューム生成時のコンテナからのデータコピーを無効
- tmpfs : tmpfsオプション
    - size : tmpfsマウントのサイズ


### ⑦ unixソケットをマウント

```
- type: volume # ⑦ unixソケットをマウント
        source: php-fpm-socket
        target: /var/run/php-fpm
        volume:
          nocopy: true
```

#### `type: volume` 

- ボリュームマウントを選択している。

#### `source: php-fpm-socket`

- マウント元。②で作成したボリュームを選択している。

#### `target: /var/run/php-fpm`

- ボリュームにコンテナの中の`/var/run/php-fpm`がマウントしている。

https://uteee.com/system/966/#

- 公式で [PHP のイメージは４種類](https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4#:~:text=fpm%EF%BC%88FastCGI%20Process%20Manager%EF%BC%89) 用意されているらしい。
- 今回は、 PHP-fpm のイメージを使用している。（Dockerfile で指定している。）

- PHP-fpm は FastCGI で動作させた PHP。

    - [FastCGI](https://wa3.i-3-i.info/word12806.html) は CGI を改良したもの。

        - CGI は、 Web サーバー上でプログラムを動かして、動的にホームページを作成するための仕組み。

- PHP には モジュール版と CGI 版の２種類がある
    - モジュール版は、 Apache や Nginx のプロセス内で PHP を実行する
    - CGI 版は、 Apache や Nginx とは別プロセスで PHP を実行する

- PHP-fpm と Nginx を連携させるには、 TCP か UNIX ドメインソケットのどちらかで通信する
- `/var/run/php-fpm` をマウントさせているのは UNIX ドメインソケットで通信するため

https://qiita.com/susuz/items/113002580945e94b1456


- UNIX ドメインソケットの方が TCP と比べて早い
- UNIX ドメインソケットは同じマシン上で動いているプロセスが通信を行うためのソケット

https://qiita.com/kuni-nakaji/items/d11219e4ad7c74ece748

https://qiita.com/kotarella1110/items/634f6fafeb33ae0f51dc

https://qiita.com/toshihirock/items/b643ed0edd30e6fd1f14

https://kengotakimoto.com/docker-laravel/

##### Nginx と PHP-fpm の連携のイメージ

![イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/f392edc6-3458-99bb-34ab-f0753ced716a.png)

- [UNIX ドメインソケット](https://ja.wikipedia.org/wiki/UNIX%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%B1%E3%83%83%E3%83%88)は、ファイルシステムを利用して Nginx と PHP-fpm の通信を行なっている
    - `php-fpm.sock` を使っている
- `var/run/php-fpm` 配下に `php-fpm.sock` が生成される。
- そのため、`var/run/php-fpm`にボリュームにマウントして共有することで、Nginx (Web)と PHP (App) コンテナの通信ができるようにしている

#### `nocopy: true`

https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-file-v3/#:~:text=nocopy%3A%20%E3%83%9C%E3%83%AA%E3%83%A5%E3%83%BC%E3%83%A0%E3%81%AE%E7%94%9F%E6%88%90%E6%99%82%E3%81%AB%E3%81%AF%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A%E3%83%BC%E3%81%8B%E3%82%89%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%B3%E3%83%94%E3%83%BC%E3%82%92%E7%84%A1%E5%8A%B9%E3%81%AB%E3%81%97%E3%81%BE%E3%81%99%E3%80%82

> nocopy: ボリュームの生成時にはコンテナーからのデータコピーを無効にします。

http://docs.docker.jp/engine/reference/run.html?highlight=restart#run-volume

> 「nocopy」 モードを指定すると、コンテナ内に要求したボリューム・パスに対して、
ボリュームの保存している場所に自動コピーを無効にします。
名前付きボリュームの場合は、「copy」がデフォルトのモードです。
copy モードは、バインド・マウントしたボリュームではサポートされていません。


~~ちょっとよくわからん。~~

`copy` ボリュームを生成する時に、マウントしているコンテナ内のディレクトリのデータを自動的にコピーする設定らしい。
ボリュームの生成時にはボリュームは空で生成して欲しいから、ここは `nocopy` になっている。


### ⑧ Laravel のソースコードをマウント

```
 - type: bind # ⑧ Laravel のソースコードをマウント
        source: ./backend
        target: /work/backend
```

- バインドマウント
    - ホスト側を書き換えるとコンテナ内も書き換わる
- `./backend` を `/work/backend` にマウントしている
    - `./backend` 内の Laravel のコードを書き換えると、コンテナ内に反映させることができる

### ⑨ psysh をマウント

```
- type: volume # ⑨ psysh をマウント
   source: psysh-store
   target: /root/.config/psysh
   volume:
     nocopy: true
```

https://github.com/bobthecow/psysh

https://www.larajapan.com/2017/12/27/%E3%82%82%E3%81%A3%E3%81%A8%E3%83%86%E3%82%A3%E3%83%B3%E3%82%AB%E3%83%BCtinker%E3%82%92%E4%BD%BF%E3%81%8A%E3%81%86%EF%BC%81/

- Laravel の thinker が psysh のラッパーらしい

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

- プルリクを見る限り thinker の履歴を残したいからマウントしているみたい

### ⑩ 環境変数を設定

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

https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4#laravel%E3%81%AE%E7%92%B0%E5%A2%83%E5%A4%89%E6%95%B0%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6

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

[environment](https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-file-v3/)

[変数の置換](https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-file-v3/#environment)

> ${VARIABLE:-default} は VARIABLE がセットされていないか空文字であるときに default として評価されます。

- 環境変数を指定している

### ⑪ 公開用ポートをマッピング

```
ports: # ⑪ 公開用ポートをマッピング
  - target: 80
    published: ${WEB_PORT:-80}
    protocol: tcp
    mode: host
```

[Ports の設定](https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-file-v3/#ports)

> - target: コンテナー内部のポート。
- published: 公開ポート。
- protocol: ポートプロトコル。（tcpまたはudp）
- mode: hostは各ノード向けにホストポートを公開、またingressはロードバランスを行うためのスウォームモードポート。

#### target & published

- [.env の WEB_PORT](https://github.com/ucan-lab/docker-laravel/blob/main/.env.example#L1) で設定されているポートと nginx の 80 ポートをマッピングしている
    - WEB_PORT を設定していない場合は 80 番 `${WEB_PORT:-80}`
- http://localhost:80 にアクセスすると nginx の 80 番ポートにアクセスすることができる

#### protocol

- tcp での通信を指定

#### mode

[mode](https://matsuand.github.io/docs.docker.jp.onthefly/compose/compose-file/compose-file-v3/#:~:text=mode%3A%20host%E3%81%AF%E5%90%84%E3%83%8E%E3%83%BC%E3%83%89%E5%90%91%E3%81%91%E3%81%AB%E3%83%9B%E3%82%B9%E3%83%88%E3%83%9D%E3%83%BC%E3%83%88%E3%82%92%E5%85%AC%E9%96%8B%E3%80%81%E3%81%BE%E3%81%9Fingress%E3%81%AF%E3%83%AD%E3%83%BC%E3%83%89%E3%83%90%E3%83%A9%E3%83%B3%E3%82%B9%E3%82%92%E8%A1%8C%E3%81%86%E3%81%9F%E3%82%81%E3%81%AE%E3%82%B9%E3%82%A6%E3%82%A9%E3%83%BC%E3%83%A0%E3%83%A2%E3%83%BC%E3%83%89%E3%83%9D%E3%83%BC%E3%83%88%E3%80%82)

> mode: hostは各ノード向けにホストポートを公開、またingressはロードバランスを行うためのスウォームモードポート。

~~つまりどういうことだろ？ わからん。~~

`hostは各ノード向けにホストポートを公開` 

コンテナのポートを公開するよってことらしい。
ポートを公開しているおかげでホスト側からコンテナのポートにアクセスできる。
ただし、`host` か `ingress` しか設定はない。
ロードバランスをしなくていいなら `ingress` にする必要はない。


### ⑫ unixソケットをマウント

- PHP コンテナ (app) と同じように unix ソケットをマウントしている。
- PHP コンテナに port が設定されていないのは unix ソケットで nginx と通信できているため
   - PHP コンテナに外部から直接アクセスすることはない
   - nginx コンテナに PHP はインストールされていないが、 PHP コンテナと連携することで PHP の処理を行うことができる
   - [AP サーバー](https://kitsune.blog/ap-server-summary)のイメージ


### ⑬ Laravel のソースコードをマウント

- PHP コンテナと同じく Laravel のソースコードをマウント
- なぜ PHP と Nginx 両方でマウントしているのか
    - まず、 HTTP のリクエストは Nginx に送られてくる
    - 静的なコンテンツを返す
    - PHP の処理が必要な場合は PHP コンテナに処理を任せる
    - そのため、両方のコンテナに同じ Laravel のソースコードをマウントしている、という理解

（unix ソケットで通信云々ってとこがまだ理解が浅いな、、）

#### Nginx と PHP-fpm の連携のイメージ

![イメージ (2).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/18f79e4d-f84a-7571-ef0c-bd836e066ec8.png)


###  ⑭ mysqlをマウント

- mysql をマウントすることでコンテナを削除しても DB のデータが消えてしまわないようにしている


## 環境全体のイメージ

![イメージ (3).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/364773/9921ce3a-9bae-bc64-5371-1db40ca67541.png)


## おわり

docker の構成を全部理解できるようにしようと思っていましたが、Docker Compose だけでかなり長くなってしまったので Docker Compose と Dockerfile の２部構成にしようかと思います。

Dockerfile 編は多分そのうち描きます。
（アドベントカレンダーの担当の日がもう1日あるのでその時にでも書こうかな。）

明日は　@Nyokki さんの SQL アンチパターンの記事らしいです。
よろしくお願いします！

## 参考

直接リンクを貼っていないもの

https://www.amazon.co.jp/dp/B089VZXX63/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1