docker-composeでの環境構築で留意しておきたいところ

docker-composeでの環境を構築していて、色々迷うところがありました。
特に、Dockerfileとdocker-compose.ymlの責務範囲や、volumeの使い所など、気になった部分がありました。
それらについてまとめてみます。

以下は考え方の一例なので、別の視点での考え方もあるかと思います。
ご了承ください。


## 要約
Rails, bundler, yarnで Railsアプリを構成することはよくあると思います。
上記を例にして、docker-composeで環境構築する際に留意しておくと良さそうなところを挙げます。

* Dockerfileはあくまでロケール設定やマルチステージの構成に留める
* 永続化したい部分はvolumeに切り出す
* bundle install, yarn install,rails db:migrate などを流した後にアプリのコンテナをdownさせても大丈夫なようにしておく。
* .envで環境変数を分離しておいて、各自の環境でポートを調整できるようにする
* 以下でサンプルを提供しています(yarnではなくnpm版です)
  * https://github.com/undrthemt/docker-compose-rails-node-sample

## Dockerfileはあくまでロケール設定やマルチステージの構成に留める
Dockerfileで bundle install などを行なっているサンプルが沢山あります。
しかし、Dockerfileではあくまでロケール設定や、必要なソフトウェアのインストール、マルチステージの構成を行うに留めた方が良いように思います。

Dockerfileの責務が明確になりますし、bundle install や yarn install, rails db:migrateを流した後にアプリのコンテナ自体をdownさせても大丈夫です。

例えば以下のような感じです。


```bash:Dockerfile
FROM node:12.14.0 as node
FROM ruby:2.6.5

RUN apt-get update -qq && apt-get install -y postgresql-client && \
    apt-get install -y locales

COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /usr/local/include/node /usr/local/include/node
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs && \
    ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm

ENV YARN_VERSION 1.21.1

COPY --from=node /opt/yarn-v$YARN_VERSION /opt/yarn
COPY --from=node /usr/local/bin/node /usr/local/bin/

RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
    && ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

ENV BUNDLER_VERSION 2.1.3

RUN gem install bundler -v $BUNDLER_VERSION

RUN mkdir -p /var/www/web
WORKDIR /var/www/web

# Add a script to be executed every time the container starts.
COPY ./docker/entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

RUN locale-gen ja_JP.UTF-8
# timezoneをJSTに変更
RUN apt-get install -y tzdata \
  && rm /etc/localtime \
  && ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime \
  && dpkg-reconfigure -f noninteractive tzdata
# キャッシュ削除
RUN rm -rf /var/lib/apt/lists/*
EXPOSE 3000
```

### entrypoint.sh
サンプルではentrypoint.shをENTRYPOINTに登録しておくことで、コンテナ開始時に毎回このスクリプトが呼ばれるようにしてあります。
このスクリプトでは、例えば以下のような処理を行います

```bash
#!/bin/bash
set -e

# 残ったままになっている可能性のある、Railsのserver.pidを削除する
rm -f /var/www/web/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"
```


## 永続化したい部分はvolumeに切り出す
postgresなどのDBのデータは当然 volume に切り出しているのではないでしょうか。
同じように、他にも永続化しておきたい部分は、volumeに切り出すようにすると良いと思います。
これには、bundlerでのgemのインストール先や、yarnでの node_modules のインストール先も含まれます。

また、上記のような部分をvolumeに切り出すことで、
bundle install , yarn install などを流した後に、アプリ自体のコンテナをdownさせても、volumeに永続化されているため、installしたデータは残したままにすることができます。

ちなみに以下は vendor/bundle 以下に bundle install している場合の例です。


```yml:docker-compose.yml
version: '3'
services:
  web:
    build: "."
    stdin_open: true
    tty: true
    ports:
      - $HTTP_PORT:3000
    # image構築で bundle install, yarn install せず、起動時に行います。
    # gem と node-modulesは volume で永続化しています。
    command: /bin/sh -c "bundle install && yarn install --check-files && bundle exec rails s -p 3000 -b 0.0.0.0"
    environment:
      RAILS_ENV: $RAILS_ENV
      PG_USER: pguser
      PG_PASS: pgpass
      PG_HOST: postgres
      PG_PORT: 5432
      # bundler に gem の install 場所を伝える
      # この方法を使う場合は、.bundle/configもgitに追加しておいてください
      BUNDLE_APP_CONFIG: ./.bundle
      REDIS_URL: redis://redis:6379
    # vendor/bundle にインストールする前提です
    volumes:
     - .:/var/www/web
     - bundle:/var/www/web/vendor/bundle
     - node-modules:/var/www/web/node_modules
    depends_on:
      - postgres
      - redis

  postgres:
    restart: always
    image: postgres:10.6
    ports:
      - $DB_PORT:5432
    environment:
      POSTGRES_USER: pguser
      POSTGRES_PASSWORD: pgpass
      TZ: Asia/Tokyo
    volumes:
      - pgvolume:/var/lib/postgresql/data

  redis:
    restart: always
    image: redis:3.2.11
    environment:
      TZ: Asia/Tokyo
    ports:
      - $REDIS_PORT:6379
    command: redis-server --appendonly yes

volumes:
  pgvolume:
  bundle:
  node-modules:

```

上記の例のように docker-compose up 時に毎回 install を流すかどうかは好みもあるかと思います。
後から流しても別段問題ありません。

```bash
# bundle install
docker-compose exec web bundle install

# yarn install
docker-compose exec web yarn install --check-files 

# db:migrate db:seed_fu
docker-compose exec web bundle exec rails db:migrate db:seed_fu
```

## .envで環境変数を分離しておいて、各自の環境でポートを調整できるようにする
ポートや環境変数は、.envで分離しておいて、各自の環境でポートを調整できるようにしておくと、運用上楽になると思います。
例えば、複数環境をdocker-composeで起動した際にも、ポートの競合を避けることができます。
.env は .env.default として提供しておき、各自に .env としてコピーしてもらいます。

```bash:.env.default
HTTP_PORT=3000
DB_PORT=5432
REDIS_PORT=6379
RAILS_ENV=development
```

## 最後に
docker-composeに関するサンプルは沢山ありますが、沢山あるが故に、どう構成するのがベターなのか分かりにくいように思います。
公式が提供する docker-compose の構成サンプルも、部分的なものなので、上手く構成するのに Try&Error が必須です。

特に docker-compose で扱う際に、**Dockerfile にどこまでやらせるか**という部分については、他にも意見があるように思います。

こちらの記事が皆さんの参考になれば幸いです。
最後まで読んでいただき、ありがとうございました。