【サンプル付き！】自分のDockerfileを振り返ってみた（Docker✖️Rails✖️React✖️MySQL）

## はじめに
おーせです！こんにちは！
実はプログラミングスクール（RUNTEQ）に通って5ヶ月が経ちました。現在PFを作成してます！
せっかくだから企業で使っているような技術を吸収しながらPFを開発したいと思い、付け焼き刃覚悟でDockerでの環境構築にチャレンジしました。
そして気づけば年末なので（？）、自分のDockerfileを振り返るという記事を書いてみます。
## この記事で触れること
この記事では主にDockerfileの内容について触れていきます。
先月Dockerでの環境構築はできたものの、なんとなく立ち上がっている感が否めないので、この際振り返って確認することにしました。
ただ今回の記事では基本的なコマンドの説明は割愛しますので、ご了承ください。
## サンプルについて
本記事で修正した環境構築前のDockerfileなどは以下のgithubで公開しております。
ご興味ある方は立ち上げてみたりして、遊んでみてください！（手順はREADMEに記載しております。）

https://github.com/ooooose/Docker_setup

## 使用技術について
以下の技術を念頭に環境構築をしました。
今回はバックエンドとフロントエンドそれぞれにDockerfileを作成しています。
　-　Ruby（v2.7.4）
　-　Rails APIモード（v6.1.7）　
　-　React（v18）
　-　MySQL@5.7

## 環境構築の方法について
以下の順序で作業しました。
①　ホスト環境でReactアプリをセットアップ
②　フロントエンド用のdockerfileを作成し、frontディレクトリに格納
③　バックエンド側のDockerfileやGemfile等必要なファイルを作成
④　バックエンド側の環境構築（コマンドの実行）　
⑤　`docker-compose up -d --build`でビルドして立ち上げ。
これら手順はgithubのリポジトリでまとめておりますので、そちらをご参照いただけると嬉しいです。
## バックエンドのDockerfileについて
まずはDockerfileを作成します。最初のものを下に載せます。
```:Dockerfile（api側）
FROM ruby:2.7.4
ENV TZ Asia/Tokyo

# 必要なパッケージをインストール
RUN apt-get update 
RUN apt-get install -y \
    build-essential \
    libpq-dev \
    nodejs \
    default-mysql-client \
    yarn

# 作業ディレクトリを指定
WORKDIR /myapp

# ローカルからコンテナにファイルをコピー
COPY Gemfile Gemfile.lock /myapp/
RUN bundle install
COPY . /myapp

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
```
それぞれどういった意図で記載をしたか分割して説明していきます。まずはこちらから。
```:Dockerfile(api側抜粋)
FROM ruby:2.7.4
ENV TZ Asia/Tokyo

# 必要なパッケージをインストール
RUN apt-get update 
RUN apt-get install -y \
    build-essential \
    libpq-dev \
    nodejs \
    default-mysql-client \
    yarn
```
まず、ruby（v2.7.4）を使用して環境構築をするので、ベースとなるRuby2.7.4のDockerイメージを指定します。
２行目の`ENV TZ Asia/Tokyo`では、コンテナのタイムゾーンを東京に設定しています。（ENVコマンドではコンテナ内の環境変数を指定できます。）
参照させていただいた多くのDockerfileに記載されていたので記載しました。
　
次にRUNコマンドで必要なパッケージのインストールです。
`apt-get update`では、必要なインストールできるパッケージのリストを最新にするコマンドです。
その後、`apt-get install -y [パッケージ名]`とすることで必要なパッケージを最新でインストールできます。また、`-y`はインストール中に、YesかNoか複数回にわたって聞かれるのですが、それらに対して自動的に「yes」と返すためのオプションになります。
そして、今回導入したパッケージは次の５つです。必要性も含めて検証していきます。
### build-essential
Ubuntu Linux/Debianにおいて、C/C++のビルド環境を整えるために必要なパッケージみたいです。
Ruby自体C言語で書かれていることを考慮すると、ビルドする際にはあった方が良いかと思われます。
### libpg-dev
PostgreSQLをインストールする際に必要なパッケージです。
今回使用するのはMySQLでして、`rails new`する時点でデフォルトのDBをMySQLにすれば不要と判断して、今回は削除してみました。
### nodejs
その名の通り、Node.jsを実行する環境を整えるパッケージです。
しかし、今回Rails apiモードでの開発であり、フロントはreactで別に開発するため、不要と判断して、こちらも削除します！  
### default-mysql-client
こちらはMySQLの環境関連のパッケージなので、削除せず据え置きとします。
### yarn
こちらもnodejs同様で、yarnはバックエンド側では使用しないので不要と判断し、削除いたします！！

最後に、RUNを２行に分けて実行していますが、これも悪手だと思われます。
**RUNコマンドはDockerの新たなイメージレイヤを追加する**ため、コンテナのボリュームが大きくなってしまいます。（RUNだけではありませんが...）
これらを踏まえて、上記コマンドについては以下のように改善できるかと思います！

上のDockerfile抜粋部分を改善したコードは以下のとおりです。
```:Dockerfile(改善案)
FROM ruby:2.7.4
ENV TZ Asia/Tokyo

# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
    build-essential \
    default-mysql-client 
```

次にこちらです。
```:Dockerfile(api側抜粋)
# 作業ディレクトリを指定
WORKDIR /myapp

# ローカルからコンテナにファイルをコピー
COPY Gemfile Gemfile.lock /myapp/
RUN bundle install
COPY . /myapp
```
WOKRDIRコマンドは、コンテナ内での作業ディレクトリを作り、指定する（今回はmyapp/と指定）コマンドになっています。
ここの部分`RUN mkdir /myapp`を上に挟むことも考えたのですが、WORKDIRはディレクトリがなければ作ってくれるみたいなので、不要と判断しました。
 
その後、COPYコマンドでローカルにあるGemfileとGemfile.lockをコンテナ内のmyappディレクトリに追加します。
このCOPYコマンドもGemfileとGemfile.lockで行を分けているケースがありますが、COPYコマンドもコンテナにレイヤーを追加するようです。
そのため、１行にまとめて極力コンテナのボリュームを軽減することにしました。
 
`RUN bundle install`の行では、コンテナ内で`bundle install`を実行し、最後のCOPYコマンドでローカルのカレントディレクトリ内の全ファイルをコンテナに追加しています。
ここは据え置きでいいかな、と思います。

最後にこちらの行です。
```:Dockerfile(api側抜粋)
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
```
こちらは、ローカルにあるentrypoint.shをコンテナのディレクトリにコピーして実行しています。
上４行によって、コンテナ内のアプリを3000番ポートに（EXPOSE）公開している状態になります。

最後のCMDコマンドは指定したコマンドを実行するコマンドになります。
今回は同一のDockerfile内でENTRYPOINTと一緒に使っているため、ENTRYPOINTで指定したコマンドの引数をCMDでは設定していることになります。
つまり、 `ENTRYPOINT ["entrypoint.sh"]`の実行をする際の引数として、CMDにより`["rails", "server", "-b", "0.0.0.0"]`を渡しているということです。
最終的には`entrypoint.sh`に記載されている`exec "$@"`に渡されて実行されていることになります。
　
`["rails", "server", "-b", "0.0.0.0"]`の内容も少しだけ説明します。
`rails server`はrailsアプリを立ち上げるコマンドですが、`--bind=0.0.0.0`により、同一ネットワーク内のIPアドレス全てからのアクセスを可能にしています。
このオプションにより、コンテナの外からでもアプリにアクセスすることができるようになっています。
この部分については特段変更なしで大丈夫と思われます。

## 改善後のDockerfile
あまり変わってはいませんが、以下のようになりました。
```:Dockerfile(api側改善後)
FROM ruby:2.7.4
ENV TZ Asia/Tokyo

# 必要なパッケージをインストール
RUN apt-get update && apt-get install -y \
    build-essential \
    default-mysql-client

# 作業ディレクトリを指定
WORKDIR /myapp

# ローカルからコンテナにファイルをコピー
COPY Gemfile Gemfile.lock /myapp/
RUN bundle install
COPY . /myapp

COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]
EXPOSE 3000

CMD ["rails", "server", "-b", "0.0.0.0"]
```

## フロント側のDockerfileについて
こちらは下のような記載になります。
環境構築自体はホスト環境で行い、その後Dockerfileをフロントのディレクトリ内に置いてビルドする、という手順を踏みました。
```:Dockerfile(front)
FROM node:19.0.0-alpine
WORKDIR /user/src/app
```
こちらはNode.jsのイメージ（軽量と言われるalpineを使用）をベースとして作り、コンテナ内にappディレクトリを作成します。
Node.jsのバージョンは導入したいreactのバージョンとの互換性を調べて、問題ないバージョンにしてください。
ここまででDockerfileの振り返りは以上となります！
最後にdocker-compose.ymlについても簡単に触れてみます。
## docker-compose.ymlについて
**docker-composeとは複数のコンテナを同時操作するために役立つコマンド**です。
docker-compose.ymlは操作方法の元となる設計書だと私は理解しています。
作成したdocker-compose.ymlは下のようになっております。
```:docker-compose.yml
version: '3'

volumes:
  mysql_data:
services:　　
  api:　
    build: 
      context: ./api/　
      dockerfile: Dockerfile
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    image: rails:dev
    volumes:
      - './api:/myapp'　
    ports:
      - '3000:3000'
    tty: true
    stdin_open: true
    depends_on:　
      - db
  db:
    platform: linux/x86_64　
    image: mysql:5.7
    volumes:
      - 'mysql_data:/var/lib/mysql'
    environment:
      MYSQL_ROOT_PASSWORD: password
    ports:
      - '3306:3306'
  front:
    build:
      context: ./front/
      dockerfile: Dockerfile
    volumes:
      - './front:/user/src/app'
    environment:
      - WDS_SOCKET_PORT=0
    command: sh -c "yarn start"
    ports:
      - '8000:3000'
    tty: true
    stdin_open: true
```
1行目の`version: '3'`ではdocker-composenのバージョンを指定しています。
2022年時点で3が最新なのでこのように記載しています。
docker-composeで使用するコマンドの説明を簡単にできればと思います。

以下、コマンドの説明をします。
（私なりに調べ噛み砕いたつもりですが、間違ってたらご指摘お願いします。）
|コマンド  |説明  |
|---|---|
|build  |指定したDockerfileをビルドする |
|command |実行するコマンドを指定し、実行する |
|image  |実行するコンテナのイメージを指定する |
|volumes  |ホストとコンテナ内をバインドマウントし、共有可能にする  |
|environment  |コンテナ内の環境変数を設定して管理する |
|ports  |コンテナ内で公開されているポートとホストのポートを接続する  |
|tty  |コンテナ内で作業する場合の出力結果をきれいに表示する |
|stdin_open  |コンテナ内での標準入出力を可能にする |
|depends_on  |指定したサービスとの依存関係を設定する |

それぞれのコマンドの役割に応じて、Dockerfileと齟齬がないようにコマンドの内容を指定します。
docker-compose.ymlの内容はアプリ作成を進める中で追加したものも記載しています。
## 終わりに
以上で、自分のDockerfileを振り返る記事を終了します。
結局修正したのはapi側のDockerfileの冒頭だけでしたが、記事作成を通して曖昧に勉強してきた知識を少しは整理できたかな、と思います。
これからDockerライフを始める皆さんに少しでも参考になれば嬉しいです。
不備ありましたら、確認し随時更新しますのでご指摘お願いします。では！