【コピペOK！失敗しない！】Ruby on Rails 6系のDocker環境構築(Ruby 2.6.6、Rails6.0.3、Mac)

## はじめに
DockerでRuby on Railsの環境構築をするにあたり、色々な記事を読み比べ、エラーが出ては周りのエンジニアとして活躍されている方達にアドバイスをいただきました。

そうしていくうちに自分なりの「解」を見出せたので、自分が忘れないように、また他の方々の参考になればと思い当記事を作成いたしました。

## RailsとRubyのバージョンについて
![B08D3DW7LP.01._SCLZZZZZZZ_SX500_.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366336/9f92b5d1-d3d7-650e-6f6d-9c53b806a9c4.jpeg)
画像引用元：Amazon 「パーフェクト Ruby on Rails 【増補改訂版】 」


上記画像の著書「[パーフェクト Ruby on Rails 【増補改訂版】](https://www.amazon.co.jp/%E3%83%91%E3%83%BC%E3%83%95%E3%82%A7%E3%82%AF%E3%83%88-Ruby-Rails-%E3%80%90%E5%A2%97%E8%A3%9C%E6%94%B9%E8%A8%82%E7%89%88%E3%80%91-Perfect/dp/4297114623/ref=asc_df_4297114623/?tag=jpgo-22&linkCode=df0&hvadid=342520995287&hvpos=&hvnetw=g&hvrand=2041711243397193021&hvpone=&hvptwo=&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9053313&hvtargid=pla-918633009109&psc=1&th=1&psc=1&tag=&ref=&adgrpid=69995157558&hvpone=&hvptwo=&hvadid=342520995287&hvpos=&hvnetw=g&hvrand=2041711243397193021&hvqmt=&hvdev=c&hvdvcmdl=&hvlocint=&hvlocphy=9053313&hvtargid=pla-918633009109)」で **Ruby 2.6.6**と **Rails 6.0.3**の組み合わせを推奨しているので、僕も同じバージョンで環境構築しました。

ただ、著書内には”一番新しいRubyが一番よいRubyなので、余裕がある方は新しいバージョンで試して欲しい”との書き込みもありましたので、すでに慣れている方はぜひトライしてみてください。

## 参考にした記事一覧
僕の場合は環境構築をするにあたり、1つの記事だけを参考にするのではなく、5〜6記事を比較しながら取捨選択しました。

最終的に参考にさせていただいた記事は以下の通りです。

参考記事①：「[DockerでRuby on Railsの環境構築を行うためのステップ【Rails 6対応】](https://qiita.com/kodai_0122/items/795438d738386c2c1966)」
参考記事②：「[docker　ruby(2.6.5)・mysql(5.6.47)・rails(6.0.0)の開発環境構築](https://qiita.com/Tomohisa_koyama/items/5f2d32fd0677aa244b72)」
参考記事③：「[【Rails6】Dockerによる開発環境構築](https://jpdebug.com/p/2469468)」
参考記事④：「[Ruby on Rails 6のDocker環境構築](https://zenn.dev/yuma_ito_bd/scraps/1adb89dfe0661c)」
参考記事⑤：「[Dockerを使ってRails6環境の構築をしてみる](https://qiita.com/me-654393/items/ac6f61f3eee66380ecd7)」

各記事の執筆者様には本当にお世話になりました。この場を借りて本人には届かないと思いますが謝辞を述べさせてください。 **ありがとうございます！！！！！！**

## 環境構築の手順
早速DockerでRailsの環境構築を行う手順を解説するのですが、以下を前提として進めさせていただきます。

・Macを使用している方
・Docker for Macのインストールが済んでいる方

また、環境構築を進めていく中でつまづいたところ(エラーなど)を、「**※つまづいたところ**」として修正した手順も載せています。もし、同じエラーが出た方は参考にしてください。

### 1. アプリのディレクトリを作成する
アプリを開発するディレクトリを作成します。(僕の場合、cdコマンドでデスクトップに移動し、デスクトップ直下に作成しました。)

ここでは例として、「myapp」という名前のディクレトリを作成します。

```terminal:ターミナル
mkdir myapp
```

cdコマンドで、myappに移動します。

```terminal:ターミナル
cd myapp
```

### 2. Dockerfileを作成する
移動したmyappディレクトリ内に、空の`Dockerfile`を作成します。

```terminal:ターミナル
touch Dockerfile
```

このDockerfileに、Dockerイメージを構築するための手順を記載します。VScodeなどのテキストエディタまたはターミナル上でviコマンドを使用して以下のように`Dockerfile`を編集しましょう。

```Dockerfile:Dockerfile
FROM ruby:2.6.6

# Node.jsをインストール
RUN curl -sL https://deb.nodesource.com/setup_16.x | bash - && apt-get install -y nodejs

# yarnパッケージ管理ツールをインストール
RUN 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

# gem
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

# Configure the main process to run when running the image
CMD ["rails", "server", "-b", "0.0.0.0"]
```

FROMやRUN、WORKDIR、COPYなどの先頭のコマンドについては、それぞれ何の意味があるのか検索して調べることをおすすめしますが、ざっくり解説すると以下の通りです。

**FROM** 使用するベースイメージ(ここではRuby)とバージョンの指定
**RUN**	コマンドの実行。パッケージのインストールなどで使用
**WORKDIR**	WORKは作業、DIRはディレクトリ、つまり「作業ディレクトリ」の変更。ターミナルでのcdと一緒
**COPY** 指定したローカル内のファイル・ディレクトリをコンテナ内にコピー
**ENTRYPOINT** docker run時に最初に実行されるコマンド(ここでは`entrypoint.sh`を参照)
**EXPOSE** 公開するポートの指定(http://localhost:3000/ の3000)
**CMD**	docker run時に実行されるコマンド

ちなみに、Rails 6系からはwebpackを利用するので、Node.jsとYarnもインストールする必要があるので、RUN curlでインストールしています。

#### ※つまづいたところ
別記事ではnodeのインストールのコマンドが、「https://deb.nodesource.com/setup_7.x 」となっていて、そのまま実行したら以下のエラーが出ました。

```terminal:ターミナル
Installing dev server for live reloading
         run  yarn add --dev webpack-dev-server from "."
yarn add v1.22.18
[1/4] Resolving packages...
[2/4] Fetching packages...
error http-proxy-middleware@2.0.6: The engine "node" is incompatible with this module. Expected version ">=12.0.0". Got "10.24.0"
error Found incompatible module.
```

なので、僕のDockerfileでは「setup_7.x」の部分を「setup_16.x」に変更しています。

### 3. Gemfileを作成する
myappディレクトリ内に、空の`Gemfile`を作成します。

```terminal:ターミナル
touch Gemfile
```

生成した`Gemfile`を以下のように編集します。

```Ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '6.0.3'
```

インストールするRailsのバージョンを指定しています。この`Gemfile`の内容は後にrails newを実行した際に中身が書き換えられます。

### 4. Gemfile.lockを作成する
myappディレクトリ内に、空の`Gemfile.lock`を作成するだけで大丈夫です。

```terminal:ターミナル
touch Gemfile.lock
```

### 5. entrypoint.shを作成する
まずmyappディレクトリ内に、空の`entrypoint.sh`を作成します。

```terminal:ターミナル
touch entrypoint.sh
```

作成した`entrypoint.sh`を以下のように編集します。
```Ruby: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 -f /myapp/tmp/pids/server.pid]**　 server.pidファイルが存在している場合に削除する。
**[exec "$@"]**　　`Dockerfile`のCMDで渡したコマンド（rails server -b 0.0.0.0）を実行

server.pidファイルはなんぞやって方はご自身で調べていただけたらと思います。

ただ、`entrypoint.sh`に上記記述をしないと、後々docker-compose up -dでコンテナを立ち上げる時、「なぜかコンテナがExitになっていて立ち上がらない！！汗」って事件が起きるんですよ...。

その諸悪の根源がserver.pidファイルが存在しているせいってことが多いんですよね。それを`entrypoint.sh`で削除するように指定しているわけですね。

#### ※つまづいたところ
`entrypoint.sh`でも実はつまづいたところがありまして、`entrypoint.sh`の一行目のコメントである

```Ruby:entrypoint.sh
#!/bin/bash
```
こいつを不要なものだと思って削除してたんですよ最初。

あとあと以下のエラーが出て、

```terminal:ターミナル
standard_init_linux.go:228: exec user process caused: exec format error
ERROR: 1
```
調べてみるとどうやら、entrypoint.shファイルからshebang(実行時にインタプリタを指定するもの)が抜けていたことが原因らしく、

一行目の **#!/ bin/sh**はただのコメントではないので削除してはいけないらしいんですよ。

参考記事：「[exec user process caused “exec format error”で少し詰まった](https://chibiprogrammer.dev/2020/09/14/exec-user-process-caused-exec-format-error%E3%81%A7%E5%B0%91%E3%81%97%E8%A9%B0%E3%81%BE%E3%81%A3%E3%81%9F/)」

### 6. docker-compose.ymlを作成する
まずmyappディレクトリ内に、空の`docker-compose.yml`を作成します。

```terminal:ターミナル
touch docker-compose.yml
```

次に作成した`docker-compose.yml`を以下のように編集します。僕はMySQLを利用するので以下のようにしました。

```Ruby:docker-compose.yml
# docker-composeのバージョンを指定
version: "3"
services:
  db:
    # 使用するイメージを指定(dbでmysqlを指定しています)
    image: mysql:8.0
    # ディレクトリのマウント設定（dbデータなどを残せます）
    volumes:
      - ./tmp/db:/var/lib/mysql
    # mysqlのパスワードを指定。環境変数を定義
    environment:
      MYSQL_ROOT_PASSWORD: password
    # ポート番号の指定。[ホスト：コンテナ]で設定
    ports:
        - "3306:3306"
  web:
    # Dockerfileがあるパス
    build: .
    # 「server.pidファイルを削除」と「rails s」を実行するコマンド
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/myapp
      - gem_data:/usr/local/bundle
    ports:
      - "3000:3000"
    # 依存関係を設定。dbが起動してからwebへ起動することになる
    depends_on:
      - db
# データとgemを永続化する
volumes:
  mysql_data:
  gem_data:
```

PostgreSQLを利用する場合は、こちらの記事「[Ruby on Rails 6のDocker環境構築](https://zenn.dev/yuma_ito_bd/scraps/1adb89dfe0661c)」を参考にしてください。

### 7. Railsプロジェクトを作成する
これまで作成した5つのファイルをもとに、docker-compose runを実行してRailsアプリケーションを作成していきます。コマンドは以下の通りです。

```terminal:ターミナル
docker-compose run --no-deps web rails new . --force --database=mysql
.
.
.
.
.
.
# 最後にWebpackerのインストール成功メッセージが表示される 
Done in 115.80s.
Webpacker successfully installed 🎉 🍰
```

**[--no-deps]**　リンクしたサービスを起動しない
**[--force]**　　既存のGemfileを上書きするためのオプション
**[--database=mysql]**　DBにmysqlを指定

上記コマンドを実行すると、Dockerfileを元にwebのイメージがビルドされ、生成されたコンテナ(web)の中でrails newされます。

上手く実行されればRails newした際に生成される各フォルダ・ディレクトリがあると思うので確認してください。

#### ※つまづいたところ
rails newしたあとに`Gemfile`を確認したところ、railsを6.0.3で固定していたはずが以下のようになっていました。

```Ruby:Gemfile
gem 'rails', '~> 6.0.4', '>= 6.0.4.7'
```

また、後々解説するdocker-compose upでコンテナを起動させたところ、ログに以下のエラー文が出ていたので、

```terminal:ターミナル
Bundler::GemNotFound: Could not find gem 'webpacker (~> 5.0)' in any of the gem sources listed in your Gemfile.
```

Railsのバージョンを6.0.3に固定するのと同時にwebpackerを~> 4.0から~> 5.0に変更しました。

```Ruby:Gemfile
gem 'rails', '6.0.3'
..
..
..
gem 'webpacker', '~> 5.0'
```
以下のコマンドでbundle updateして、
```terminal:ターミナル
docker-compose run web bundle update
```
docker-compose downを行い、
```terminal:ターミナル
docker-compose down
```
docker-compose up --buildを実行したところ、無事エラーがなくコンテナが起動できました。
```terminal:ターミナル
docker-compose up --build
```


### 8. Dockerイメージをビルドする
先ほどのrails newコマンドによって、Gemfileが書き換わったので、Dockerイメージをビルドします。その際にbundle installもされます。

コマンドは以下の通りです。

```terminal:ターミナル
docker-compose build
```

### 9. データベース接続情報を設定
データベースの情報を設定するために、`config/database.yml`の中身を`docker-compose.yml`で設定したDB情報に書き換えます。

```yml:config/database.yml
# 設定箇所のみ抜粋
default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password 
  host: db

.
.
.
development:
  <<: *default
  database: myapp_development
.
.
.
test:
  <<: *default
  database: myapp_test
```

### 10. データベースを作成する
下記のコマンドでデータベースを作成します。

```terminal:ターミナル
docker-compose run web bundle exec rails db:create


# 作成が成功すると、以下のコマンドが表示される。
Starting myapp_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'
```

#### ※つまづいたところ
**docker-compose run web bundle exec rails db:create** をしたところ、下記のエラーが出てしまいました。

```terminal:ターミナル
Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory
Couldn't create 'myapp_development' database. Please check your configuration.
rails aborted!
Mysql2::Error::ConnectionError: Plugin caching_sha2_password could not be loaded: /usr/lib/x86_64-linux-gnu/mariadb19/plugin/caching_sha2_password.so: cannot open shared object file: No such file or directory
```
こちらの記事「[【docker】db:createすると、Plugin caching_sha2_password could not be loaded...のエラーハマった話](https://qiita.com/tomo-IR/items/224d33f14561e759dd16)」によると、原因は **”Mysql 8以降は認証プラグインの仕様が変わったため"** とのことでした。

そのため、以下の手順でDocker内のmysqlにログインし、ユーザーのプラグインを変更していきます。

**手順①：コンテナ内のMysqlにログインするため、コンテナIDを調べる**
```terminal:ターミナル
$ docker ps

CONTAINER ID   IMAGE           COMMAND                  CREATED       STATUS         PORTS                               NAMES
8bb9c2d8f943   mysql:8.0       "docker-entrypoint.s…"   3 hours ago   Up 7 minutes   0.0.0.0:3306->3306/tcp, 33060/tcp   XXX-XXX_db_1
```

**手順②：コンテナIDがわかったので、dbコンテナ内に入る**
```terminal:ターミナル
docker exec -it 8bb9c2d8f943 bash
```
**手順③：mysqlに入る**
```terminal:ターミナル
# 上記のコマンドでdbコンテナ内に入ると、以下のようになっている
root@8bb9c2d8f943:/#

# 「mysql -uroot -p」コマンドを実行する
root@8bb9c2d8f943:/# mysql -uroot -p
```
**手順④：mysqlのパスワードを入力**
以下の画面が出たら、mysqlのパスワードを入力します。(僕の場合、`config/database.yml`を見たらわかると思いますが、パスワードが「password」になっているのでこれを入力)
```terminal:ターミナル
Enter password: 
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.23 MySQL Community Server - GPL

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
```
**手順⑤：現在のプラグインの状況を確認する**
「 **mysql** >」と表示されるので、以下のコマンドを実行して、現在のプラグインの状況を確認します。
```sql:ターミナル
mysql> SELECT user, host, plugin FROM mysql.user;


# 以下が表示される
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| root             | %         | caching_sha2_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | caching_sha2_password |
+------------------+-----------+-----------------------+
```
プラグインの部分が「caching_sha2_password」になっていますね。これが原因でrails db:createでエラーが出たようです。

**手順⑥：root user部分の2箇所だけプラグインを変更する**
下記の2つのコマンドを実行して、root user部分の2箇所だけプラグインを変更します。('password'のところは手順④と同じDBのパスワードを入力します)
```sql:ターミナル
mysql> ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

# 成功すると以下が表示される
Query OK, 0 rows affected (0.01 sec)
```
```sql:ターミナル
mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY 'password';

# 成功すると以下が表示される
Query OK, 0 rows affected (0.01 sec)
```
**手順⑦：プラグインが変更できたか確認する**
もう一度下記のコマンドを実行して、root user部分の2箇所のプラグインが変更されているか確認します。
```sql:ターミナル
mysql> SELECT user, host, plugin FROM mysql.user;
```
上手く変更できていれば、以下のようになっているはずです。
```sql:ターミナル
+------------------+-----------+-----------------------+
| user             | host      | plugin                |
+------------------+-----------+-----------------------+
| root             | %         | mysql_native_password |
| mysql.infoschema | localhost | caching_sha2_password |
| mysql.session    | localhost | caching_sha2_password |
| mysql.sys        | localhost | caching_sha2_password |
| root             | localhost | mysql_native_password |
+------------------+-----------+-----------------------+
5 rows in set (0.00 sec)
```

上手く変更できたら、まずexitコマンドでmysqlから抜けて、
```sql:ターミナル
mysql> exit
```
もう一度exitコマンドを実行してdbコンテナからも抜けましょう。
```terminal:ターミナル
root@8bb9c2e8f963:/# exit
```
**手順⑧：再度データベースを作成する**
下記のコマンドで再度データベース作成を試みます。

```terminal:ターミナル
$ docker-compose run web bundle exec rails db:create


# 作成が成功すると、以下が表示される。
Starting myapp_db_1 ... done
Created database 'myapp_development'
Created database 'myapp_test'
```
無事に成功されているはずです。

### 11. Dockerコンテナを起動し、ローカル環境のページにアクセスする
以下のコマンドでDockerコンテナを起動させましょう。
```terminal:ターミナル
docker-compose up
```
ちなみに、以下のように「-d」オプションをつけてバックグラウンドで起動するようにするのが一般的です。

```terminal:ターミナル
docker-compose up -d
```
バックグラウンドでコンテナが起動されるので、もしログを確認したい場合は「docker-compose logs」コマンドでログを確認できます。

ブラウザでhttp://localhost:3000 にアクセスして、以下の画面が表示されれば環境構築完了です。お疲れ様でした！

![https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_248644_f9cedd13-8475-39fd-828a-27e443a549e5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/366336/767943b1-7c8e-0639-d0b7-f5fff8523761.png)

もし、上記画面が表示されなかった方は上手くコンテナが起動していない可能性があります。docker-compose psでコンテナの状況(ステータス)を確認してみましょう。

```terminal:ターミナル
docker-compose ps


# 以下のようにコンテナの状況を確認できます。
    Name                    Command               State                  Ports
---------------------------------------------------------------------------------------------
myapp-db-2    docker-entrypoint.sh mysqld      Up       0.0.0.0:3306->3306/tcp, 33060/tcp
myapp-web-1   entrypoint.sh bash -c rm - ...   Exit 1
```

上記の場合、「myapp-web-1」のステータスが「Exit」になっていますね。なぜExitになっているかログを確認して原因を探る必要があります。

docker-compose upに「-d」オプションをつけてバックグラウンドでコンテナを起動した場合、docker-compose logsでログを確認することができます。

```terminal:ターミナル
docker-compose logs


# 以下のようにwebとdbのログが確認できます。
myapp-db-2 | 2022-04-23 11:09:54+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started.
myapp-db-2 | 2022-04-23 11:09:54+00:00 [Note] [Entrypoint]: Switching to dedicated user 'mysql'
myapp-db-2 | 2022-04-23 11:09:54+00:00 [Note] [Entrypoint]: Entrypoint script for MySQL Server 8.0.28-1debian10 started.
..
..
..
..
..
..
..
myapp-db-2 | 2022-04-23T11:10:16.515523Z 0 [System] [MY-011323] [Server] X Plugin ready for connections. Bind-address: '::' port: 33060, socket: /var/run/mysqld/mysqlx.sock
myapp-db-2 | 2022-04-23T11:10:16.515596Z 0 [System] [MY-010931] [Server] /usr/sbin/mysqld: ready for connections. Version: '8.0.28'  socket: '/var/run/mysqld/mysqld.sock'  port: 3306  MySQL Community Server - GPL.

myapp-web-1 | bundler: failed to load command: rails (/usr/local/bundle/bin/rails)
myapp-web-1 | Bundler::GemNotFound: Could not find gem 'webpacker (~> 5.0)' in any of the gem sources listed in your Gemfile.
myapp-web-1 |   /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:287:in `block in verify_gemfile_dependencies_are_found!'
myapp-web-1 |   /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:255:in `each'
myapp-web-1 |   /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:255:in `verify_gemfile_dependencies_are_found!'
myapp-web-1 |   /usr/local/lib/ruby/2.6.0/bundler/resolver.rb:49:in `start'
..
..
..
..
..
..
..
myapp-web-1 |   /usr/local/lib/ruby/2.6.0/bundler/setup.rb:20:in `<top (required)>'
myapp-web-1 |   /usr/local/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
myapp-web-1 |   /usr/local/lib/ruby/2.6.0/rubygems/core_ext/kernel_require.rb:54:in `require'
```

上記の場合、gemのwebpackerのエラーが出てます。このエラーに関しては、環境構築手順の「**7. Railsプロジェクトを作成する**」の「**※つまづいたところ**」で修正方法を記載しています。

## まとめ
いかがでしたか？無事環境構築できましたでしょうか？汗

ただ、もし失敗したとしてもエラーを読んでその都度検索して調べるのも重要だなと感じました。おかげで検索力がかなりつきました！

もしなにか間違いや質問、「こここうした方がいいよ！」などがあればコメントや修正リクエストお待ちしております！！

また、参考になった方はシェアやLGTM、ストックをしていただけるととても嬉しいです(土下座)