Webアプリケーションのためのdocker-compose.yml備忘録

# はじめに
この記事では以下で紹介した家計簿アプリ作りで用意したdocker-compose.ymlを詳細に見ていきます。

正直、参考にしたものをほとんどコピペして、一部自分で定義した部分もあるが、それもリファレンスをそのまま流用しただけで、ymlの中で何がどこを定義しているのか、自分の中で消化できていないテイタラクであったので、備忘録として自分の言葉で一つ一つ整理することにした次第です。

https://qiita.com/nate3870/items/97d3017d3d04a109c1bf

# 参考サイト
docker-compose.ymlの骨子はほとんど@niisan1banさんのものを流用させていただいた。
私なんぞより詳細に説明されているので、そちらも参照いただければ幸いです。

https://qiita.com/niisan1ban/items/f70eb0ed891568f71f9b#docker-composeyml%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%82%92%E7%B7%A8%E9%9B%86

## 構成図
※@niisan1banさんの図を引用させていただいています。
　私自身が改めて作るより遥かにわかりやすい図だったので、恐れながら使用させて頂きました。
![Qiita](https://camo.qiitausercontent.com/ef71e110dc11bb4eb88a02c481f31473ab560e77/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e61702d6e6f727468656173742d312e616d617a6f6e6177732e636f6d2f302f3636373036322f62656234323762662d306439612d363661392d386137612d3764303134373865656330382e6a706567 "Qiita")

# docker-compose.yml
ファイルの中身です。次に各項目について説明をつけていきます。
```yml:docker-compose.yml
version: '3'
services:
  db:
    # 起動するイメージを指定
    image: mysql:8.0.32-debian
    platform: linux/amd64
    build:
      context: .
      dockerfile: ./Dockerfile
    # 環境変数を設定
    environment:
      - MYSQL_ROOT_HOST=${DB_ROOT_HOST}
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_PASS}
      - TZ=${TZ}

    # ホスト側のポート:コンテナのポート
    ports:
      - '3306:3306'

    # ボリュームバインド
    volumes:
      - ./db/conf:/etc/mysql/conf.d/:ro
      - mysqldata:/var/lib/mysql
      - ./db/logs:/var/log/mysql

    #使用するネットワーク
    networks:
      - backend

  api:
    image: node:14.21.2-buster
    build:
      context: .
      dockerfile: ./Dockerfile
    environment:
      - MYSQL_SERVER=db
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_DATABASE=${DB_NAME}
      - TZ=${TZ}
      - CHOKIDAR_USEPOLLING=true

    #コンテナを起動させ続けるよう設定
    tty: true

    ports:
      - '3000:3000'

    # ソースコードを格納するフォルダをマウント
    #（ホスト側の./apiをコンテナの/appにマウント）
    volumes:
      - ./api:/app

    # 起動時のカレントフォルダを指定
    working_dir: /app

    # 起動後に実行するコマンドを指定
    command: npm run dev

    networks:
      - backend

    #依存関係（apiコンテナより先にdbコンテナが起動するように設定）
    depends_on:
      - db

  vue:
    image: node:14.21.2-buster
    build:
      context: .
      dockerfile: ./Dockerfile
    environment:
      - CHOKIDAR_USEPOLLING=true
    tty: true
    ports:
      - '8080:8080'
    volumes:
      - ./vue:/app
    working_dir: /app
    command: npm run serve
    networks:
      - backend
    depends_on:
      - api

networks:
  backend:

volumes:
  mysqldata:
```
## version
docker-composeのバージョンは3.0にしています。特に気にしていなかったが、docker公式サイトを参照する限り、version3.xを指定していれば無難な模様。

```yml
version: '3'
```
https://docs.docker.jp/compose/compose-file/compose-versioning.html#compose-file-version-3

## dbコンテナ
### image:

```yml
image: mysql:8.0.32-debian
```
docker officialの`mysql:8.0.32-debian`のイメージを使用しています。
推奨のマイナーバージョンが現在（2023/8/5）は異なるようなので、推奨のイメージに差し替えてもよいかもです。
メジャーバージョンを変更すると正常に動作しなくなる可能性があるのでお勧めしません。

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

### Dockerfile:
次の定義でcompose起動時に`Dockerfile`を別途読み込むような設定を追加しています。
```yml
    build:
      context: .
      dockerfile: ./Dockerfile
```
* context: Dockerfileが格納されているパスを指定します。
* dockerfile: 対象のDockerfileを相対パスで指定します。

（参考サイト）

https://docs.docker.jp/compose/compose-file/build.html#dockerfile

Dockerfileでは、DBで日本語が使える設定を定義しています。
これなしだと英語のデータしかDB登録できなかったので、使い物になりません。

```dockerfile:Dockerfile
FROM mysql:8.0.32-debian

# 日本語環境を追加
RUN apt-get update
RUN apt-get -y install locales-all
ENV LANG ja_JP.UTF-8
ENV LANGUAGE ja_JP:ja
ENV LC_ALL ja_JP.UTF-8
```

※Dockerfileを実際に適用させているのはdbコンテナに対してのみですが、お作法として、他のコンテナの定義でも記述だけしています。dbコンテナだけだと起動時エラーが出たので、各コンテナで横並びに設定を書いておくべきなのでしょう。

### environment:
MySQLの環境変数を設定しています。これらの変数を設定していないとコンテナ内でMySQLを使用できないので必須となります。
```yml
    environment:
      - MYSQL_ROOT_HOST=${DB_ROOT_HOST}
      - MYSQL_DATABASE=${DB_NAME}
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_ROOT_PASSWORD=${DB_PASS}
      - TZ=${TZ}
```
設定値は、docker-compose.ymlと同階層に配置した`.envファイル`に定義しています。
```text:.env
DB_ROOT_HOST=%
DB_NAME=oeconomica
DB_USER=username
DB_PASS=mypassword
TZ=Asia/Tokyo
```

### ports:
```yml
    # ホスト側のポート:コンテナのポート
    ports:
      - '3306:3306'
```
dbコンテナに接続するためのポート番号をここで指定しています。
MySQLで基本的に利用される3306番で定義していますが、ホスト側ですでに別サービスで同ポートを使用済みの場合は、別の分かりやすいポートに変更しても大丈夫です。
```yml:ホストの3306ポートが別で使われている場合
    # ホスト側のポート:コンテナのポート
    ports:
      - '33060:3306'
```

### volumes:
```yml
    volumes:
      - ./db/conf:/etc/mysql/conf.d/:ro
      - mysqldata:/var/lib/mysql
      - ./db/logs:/var/log/mysql
```

- ./db/conf:/etc/mysql/conf.d/:ro
  ホスト上の`./db/conf`ディレクトリをコンテナ内の`/etc/mysql/conf.d`ディレクトリにreadonly形式でマウントする。こうすることで、my.cnfがコンテナに読み込まれるようになる。
※`- ./db/logs:/var/log/mysql`も同じ仕組み

```zsh:ホスト側
% cd ./db/conf;ls
my.cnf
```

```bash:dbコンテナ
root@ae9fecd6b75e:/var/lib/mysql# cd /etc/mysql/conf.d/
root@ae9fecd6b75e:/etc/mysql/conf.d# ls
my.cnf
```

- mysqldata:/var/lib/mysql
`mysqldata`という名称のボリュームをコンテナ内の`/var/lib/mysql`にマウントする。
ボリュームにはDBのデータが保存されるので、ボリュームが残っている限りはコンテナを一度消そうが、保存したセーブデータそのままに家計簿を使い続けられる。
ボリュームの状態はDocker Destopでこのように見えている。

![スクリーンショット 2023-08-05 14.09.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3510731/501ec34f-b986-af97-c923-49112bc8f5ec.png)

volumeはdocker-compose.ymlの一番末尾でも記載され、これがないとvolumeが作成自体されない。
```yml
volumes:
  mysqldata:
```
## apiコンテナ
### image:

```yml
image: node:14.21.2-buster
```
docker officialの`node:14.21.2-buster`のイメージを使用しています。
メジャーバージョンがかなり古めのものでした。私の作ったアプリケーションでは特に問題なく動作していますが、node.jsのコンテナを他のアプリを載せるために利用しようと検討している方は、最近のバージョンのものをお勧めします。

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

### environment:
node.jsがデータベースに接続するための環境変数を定義します。

また、`CHOKIDAR_USEPOLLING=true`を設定することでHot Reloadが有効になります。
どういうことかというと、これを設定していないと、apiコンテナ内のソースコードを変更しても、変更が反映されなくなり、反映させるために、わざわざ再起動させる手間が生じます。

`CHOKIDAR_USEPOLLING=true`を設定すると、ソースコードの変更を検知して自動でアプリを再起動するライブラリ「nodemon」が動くようになるので、ソースコードの修正がリアルタイムで反映され、開発が効率化します。
```yml
    environment:
      - MYSQL_SERVER=db
      - MYSQL_USER=${DB_USER}
      - MYSQL_PASSWORD=${DB_PASS}
      - MYSQL_DATABASE=${DB_NAME}
      - TZ=${TZ}
      - CHOKIDAR_USEPOLLING=true
```
https://qiita.com/hoto17296/items/90f04fee8b70128ede19

### tty:
この設定を入れないと、ポートの接続待ちや実行プロセスがない場合コンテナが起動してすぐに終了する事象が起きてしまいます。
```yml
    #コンテナを起動させ続けるよう設定
    tty: true
```
要するに何かしらの仕事を命令されていないと、「オレいる意味なくね？」とコンテナ側がサボって動いてくれない。なので、tty(入出力を受付する窓口)の仕事を用意することで、常に職場にいてもらうようにするって感じです。

https://zenn.dev/hohner/articles/43a0da20181d34

### working_dir:
デフォルトのカレントディレクトリを設定する項目。
基本、編集するのは、/app以下だけなので、あらかじめカレントディレクトリとして設定しておきます。
```yml
    # 起動時のカレントフォルダを指定
    working_dir: /app
```

### command:
コンテナ起動時に実行させるコマンドを定義しています。
```yml
    # 起動後に実行するコマンドを指定
    command: npm run dev
```
npmはNode.jsをインストールしたときについてくるパッケージ管理ツールで、
`npm run dev`は package.json ファイル内の scripts セクションに定義された dev スクリプトを実行するコマンドになります。

```json:./app/package.json
{
  "name": "app",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "dev": "nodemon ./bin/www",
    "start": "node ./bin/www"
  },
  "dependencies": {
    "cookie-parser": "~1.4.4",
    "debug": "~2.6.9",
    "express": "~4.16.1",
    "http-errors": "~1.6.3",
    "jade": "~1.11.0",
    "morgan": "~1.9.1",
    "mysql2": "^3.1.2",
    "sequelize": "^6.29.0",
    "sequelize-cli": "^6.6.0"
  },
  "devDependencies": {
    "nodemon": "^2.0.20"
  }
}
```
https://qiita.com/Lyn131/items/e1603dc7872f0fea8601

### depends_on:
サービスの依存関係を定義しています。
ここでは、apiコンテナはdbがないとエラーになって動かないため、dbコンテナ起動後にapiコンテナの起動がはじまるように設定します。
```yml
    #依存関係（apiコンテナより先にdbコンテナが起動するように設定）
    depends_on:
      - db
```

今回のdocker-composeでは最終的に`db→api→vue`の順番で起動するよう依存関係を調節しています。

https://docs.docker.jp/compose/compose-file/compose-file-v3.html#depends-on

## vueコンテナ
dbコンテナ、apiコンテナの説明で概ね網羅しているので、改めての説明は割愛します。