DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】

# 本記事の目的
**DockerでRails 6に対応したRuby on Railsの環境構築を行うこと** です。

Docker公式ページ：[Quickstart: Compose and Rails](https://docs.docker.com/compose/rails/)（以下「クイックスタート」）に
Ruby on Railsの環境構築手順が書いてあります。

本記事では上記の手順に加え、**Rails 6に対応するための手順**も記載しています。
（クイックスタートではRails 5を想定しており、そこまで書かれていません）

Rails 6特有のハマりポイントもあったので、Rails 6で環境構築をしたいと考えている方はぜひ参考にしていただければと思います。

### ※注意 <2020/2/14追記>
Rails6 では Webpacker がデフォルトになり、フォルダ構成などもRails5と変わっています。
そのため、初学者の方（Railsチュートリアルに取り組んだばかりの方や、エンジニアを目指しているRails歴の浅い方）は素直にRails5を使うことをオススメします。
体験談ですが、初学者だとRails6でAjaxなどフロントの処理を書くときに非常に苦労します。

# 自身のスキルレベル
プログラミングスクールで学びながら、Railsを3ヶ月、Dockerを1ヶ月半ほど触っています。
触るのは慣れてきたものの、理解が曖昧な部分もあるので、
不適切な表現などあった場合はぜひご指摘いただけるとありがたいです。

# Dockerとは？
すでに詳しく解説されている記事がありますので、そちらを参照ください。
・[Qiita :【図解】Dockerの全体像を理解する -前編-](https://qiita.com/kotaro-dr/items/b1024c7d200a75b992fc)
・[Docker入門（第一回）～Dockerとは何か、何が良いのか～](https://knowledge.sakura.ad.jp/13265/)

またUdemyのDocker講座も評判が良いので、興味のある方は見てみると良いと思います。
[Udemy : ゼロからはじめる Dockerによるアプリケーション実行環境構築](https://www.udemy.com/course/docker-k/)

# 環境構築手順
## 1. 必要なフォルダ/ファイルを準備する
### 1-1. 作業フォルダの作成
まず、アプリを開発用の作業フォルダを任意の場所に作成します。
ここではクイックスタートのコードに倣い、「myapp」という名前のフォルダを作成するとします。
（私はオリジナルアプリの名前をつけました。この場合、以降に登場する「myapp」はその名前に置き換えます）

cdコマンドで、作業フォルダへ移動しておきましょう。

```
$ cd myapp(＝作成したフォルダ名）
```

このフォルダの中に、rails, Dockerの環境構築に必要なファイルを保存していきます。

### 1-2. Dockerfile 作成
1-1で作成したフォルダ内に、Dockerfileを作成します（以降のファイルも同様）。
このDockerfileに、Dockerイメージを構築するための手順を記載します。

クイックスタートのコードを元に、Rubyのバージョンだけ最新の2.6.5(2019年10月時点)に置き換えて下記のように作成します。

```rb:Dockerfile
FROM ruby:2.6.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

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

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
```

中身を簡単に解説します。

**[FROM]** 使用するイメージとバージョン
**[RUN]** コマンドの実行。railsに必要なnodejsとpostgeqsqlをインストールしています
**[WORKDIR]** 作業ディレクトリを設定
**[COPY]**　ローカルのファイルをコンテナへコピー
**[ENTRYPOINT]** 一番最初に実行するコマンド（ここでは`entrypoint.sh`を参照）
**[EXPOSE]** コンテナがリッスンするport番号
**[CMD]**　イメージ内部のソフトウェア実行（ここではRailsを指す）

`Gemfile`, `Gemfile.lock`, `entrypoint.sh` はこれから作っていきます。

より詳しく知りたい方は、公式ページ日本語訳を参照してください。
[Dockerfile のベストプラクティス](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)

### 1-3. Gemfile 作成

```rb:Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 6'
```

インストールするRailsのバージョンを指定します。
ここではRails 6に対応するようにしています。
Gemfileの内容は、後で`rails new`をすると書き換えられます。

### 1-4. Gemfile.lock 作成

作成だけして、中身は空のままでOKです。

### 1-5. entrypoint.sh 作成

```entrypoint.sh
#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

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

`set -e` ：　「エラーが発生するとスクリプトを終了する」の意
`rm ...`　：　「railsのpidが存在している場合に削除する」処理
`exec "$@"`　：　`Dockerfile`のCMDで渡されたコマンド（→Railsのサーバー起動）を実行

### 1-6. docker-compose.yml 作成

docker-compose.ymlは、アプリケーションを構成する各サービスを記載し、一斉に実行するための元となるファイルです。
ここでは、「db（PostgreSQLを指定）」「web（アプリケーション、ポートなどを指定）」の2つを記載しています。

```docker-compose.yml
version: '3'
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db
```

こちらも解説していきます。

**[version]** docker-composeのバージョンです。2019年10月現在、最新は'3'です。
**[services]** この下のハッシュにサービスを作りましょう。命名は自由ですが、通常はwebとdbと名付けます。
**[image]** 使用するimage（dbでPostgreSQLを指定しています）
**[volumes]** ディレクトリのマウント設定（dbデータなどを残せます）
**[build]** Dockerfileなどがあるパス（基本的にカレントディレクトリ）
**[command]** コマンド（server.pidファイルを削除してからrailsサーバー起動)
**[ports]** ポート番号。[ホスト：コンテナ]で設定。
**[depends_on]**　依存関係を示していて、起動順を指定できます。ここではdb→webへと起動します。

より詳しく知りたい方は、公式ページの日本語訳を参照してください。
[Docker Compose概要](http://docs.docker.jp/compose/overview.html)

---
これで必要なファイルの準備は完了です。
ここまでのフォルダ構成は、下記のようになっているはずです。

```
myapp
- Dockerfile
- Gemfile
- Gemfile.lock
- entrypoint.sh
- docker-compose.yml
```

## 2. プロジェクトのビルド
いよいよ、Railsアプリを作成していきます。

### 2-1. rails new
`docker-compose run`コマンドを用いて、`rails new`します。
カレントディレクトリがmyappであることを確認し、下記のコマンドを実行します。

```rb
$ docker-compose run web rails new . --force --no-deps --database=postgresql --skip-bundle
```

`--force`　：　既存のGemfileを上書きするためのオプション
`--no-deps`　：　リンクしたサービスを起動しない
`--database=postgresql`　：　DBにpostgresqlを指定
`--skip-bundle`　：　bundleをskip（最後にbundleします）

このコマンドを実行すると、`Dockerfile`を元にwebのイメージがビルドされ、生成されたコンテナの中で`rails new`されます。
完了すると、myappフォルダの中に見慣れたフォルダ構成が出来上がります。

```
myapp
- app
- bin
- config
- ...
- Dockerfile
- ...
```

処理は成功しましたが、下記のメッセージが出ました。
これからbundle installしますので、問題ありません。

```rb
Could not find gem 'pg (>= 0.18, < 2.0)' in any of the gem sources listed in your Gemfile.
Run `bundle install` to install missing gems.
```

### 2-2. bundle install
先ほどの`rails new`により、Gemfileが書き換わっています。
このGemfileの内容を元にbundle installしていきます。
下記コマンドを実行すると、Dockerイメージををビルドする際にbundle installしてくれます。

```rb
$ docker-compose build
```

### 2-3. DBへの接続設定
Railsの`config/database.yml`ファイルを、下記のように書き換えます。

```config/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password:
  pool: 5

development:
  <<: *default
  database: myapp_development


test:
  <<: *default
  database: myapp_test
```

Railsはデフォルトで、localhost上でDBが動作するようになっています。
そこで、hostを先ほど作成したdbに変更します。

### 2-4. Dockerコンテナの起動
下記コマンドでコンテナ及びアプリを起動します。

```rb
$ docker-compose up
```

ところがうまくいかず、異常終了してしまいました。

```rb
myapp_web_1 exited with code 1
```

エラーの元をたどると、下記の文章が見つかりました。

```rb
Please run rails webpacker:install Error: No such file or directory @ rb_sysopen - /myapp/config/webpacker.yml (RuntimeError)
```

調べてみると、
Rails6からWebpackerがデフォルトで導入されるようになった
→Webpackerをインストールしないとrails serverが起動できない
→yarnやNode.jsをインストールしないとWebpackerをインストールできない

ということみたいです。
したがって、`Dockerfile`を以下のように編集します。

```rb:Dockerfile
FROM ruby:2.6.5
RUN apt-get update -qq && apt-get install -y nodejs postgresql-client
########################################################################
# yarnパッケージ管理ツールをインストール
RUN apt-get update && apt-get install -y curl apt-transport-https wget && \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - && \
echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list && \
apt-get update && apt-get install -y yarn
# Node.jsをインストール
RUN curl -sL https://deb.nodesource.com/setup_7.x | bash - && \
apt-get install nodejs
#######################################################################
  :
(中略)
  :
```

コンテナを一度終了させてから、

```rb
$ docker-compose stop
$ docker-compose down
```

Webpackerをインストールするコマンドを実行します。

```rb
$ docker-compose run web bundle exec rails webpacker:install
```

すると、

```
Webpacker successfully installed 🎉 🍰
```

よし！うまくいきました。
再度コンテナを起動します。

```rb
$ docker-compose up
```

### 2-5. DB作成

最後にDBを作成して完了です。
※これまで使っていたターミナルではRailsサーバーが立ち上がっているはずなので、新規ターミナルを開いて行います。

```rb
$ docker-compose run web rake db:create
```

### 2-6. Railsが起動していることを確認
ブラウザで http://localhost:3000/ へアクセスすると、無事Railsが立ち上がっています。
<img width="806" alt="スクリーンショット 2019-10-18 11.37.20.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/465401/f1b1ef8f-411f-6de5-26d7-ef013112cd96.png">


# まとめ
DockerでRails 6に対応したRuby on Railsの環境構築を行いました。
Dockerで環境構築を行うだけならそれほど詰まることはないのですが、Rails 6に対応しようとしたことで予想以上に苦戦しました。。。
同じ事象にハマった方の参考になれば幸いです。

# 参考
下記の記事を参考にさせていただきました。
ありがとうございます。
https://qiita.com/daichi41/items/dfea6195cbb7b24f3419
https://qiita.com/m-itoidcf/items/a1ad7b982402b33189b7

また、Railsアプリ開発をする上で下記の書籍が大変参考になりオススメです。
[現場で使える Ruby on Rails 5速習実践ガイド](https://www.amazon.co.jp/現場で使える-Ruby-Rails-5速習実践ガイド-大場寧子/dp/4839962227/ref=asc_df_4839962227/?tag=jpgo-22&linkCode=df0&hvadid=295723231663&hvpos=1o1&hvnetw=g&hvrand=9348661758750508960&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=1009309&hvtargid=pla-527403649897&psc=1&th=1&psc=1)