Dockerを用いてRuby on Railsの環境構築をする方法( Docker初学者向け )

>Dockerのバージョンアップに伴い、`docker-compose.yml`を一部編集しました。
>（2021/3/19 18:10 追記）

Dockerを初めて勉強するとき、多くの方が「参考書」や「入門Dockerなどのサイト」で基礎的なところから学習すると思います。
しかし、Dockerとは何なのか、何のイメージも沸いていない状態で、様々な単語を羅列されても、さっぱりわからないという方も多いと思います。( 少なくとも私はそうでした。)
なので極論、基礎すっ飛ばしてまず実際にDockerで仮想環境を構築しながら学んでいこうという話です。
そして何となくでいいのでイメージを掴んでから、基礎を学べば良いと思います。
Dockerを初めて学習する方の中で、Ruby on Railsで開発したアプリケーションをポートフォリオとしている方も多いのではないでしょうか。
なので今回は、__Dockerを用いてRuby on Railsの環境構築をする方法__ をご紹介いたします。

ですが、超基本的なことだけ先に説明させてください。
( とにかく早く環境構築した人は __[前提](#前提)__ まで飛んでください )

## Dockerとは
![1_y6CvfE6WUgoIdT8Mp0Ev_g.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654387/61b301ea-5818-844b-2078-69596bbf84a8.png)
Dockerは __仮想環境を構築するためのツール__ です。

###- Dockerエンジンとは -
__Dockerエンジン__とは Dockerイメージの作成 や コンテナの起動 などを行う、いわばDockerの要です。

###- コンテナとは -
__コンテナ__とはDockerエンジン上で構築される__仮想環境そのもの__です。
コンテナは、`CentOS` や `Ubunts`などの OS、`Nginx` や `MySQL`などの ミドルウェア、`Rails` や `WordPress`などアプリケーションまで、様々な環境を構築することができます。

###- Dockerイメージとは -
__Dockerイメージ__は、コンテナを作成するための レシピ のようなものです。
CentOS、MySQL、Ruby などなど、他にも数えきれないほどのDockerイメージがあります。

###- Dockerfileとは -
__Dockerfile__は、オリジナルのDockerイメージを作成するための設計書のようなものです。
既存のDockerイメージをベースに、パッケージをインストールしたり、ファイルを書き換えたりした、新しいイメージを作成することができます。

###- Docker composeとは -
![docker-compose-button.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654387/3b828e25-efd0-898d-47d9-a4d5c515fa39.jpeg)
__Docker compose__ とは、複数のコンテナの構築、実行する手順を自動化するツールです。
その手順などを記したファイルが`docker-compose.yml`で、これのおかげで少ないコマンドの実行で複数のコンテナを起動することができます。

## 環境
- MacOS
- Docker 20.10.5
- docker-compose 1.28.5
- Ruby 2.5.7
- Rails 5.2.4
- MySQL 5.7

## 前提
- `Docker`および`Docker compose`のインストールを済ませてください
- これ以降の説明の`myapp`の部分はご自身のアプリケーション名に変えてください
- とにかく早く環境構築したい方は、説明を飛ばし、コピペして コマンドを実行してください。

## アプリケーションの作業ディレクトリを作成
`mkdir`で作業ディレクトリを作成します。

```
$ mkdir myapp
```

## 必要なファイルを用意
ファイル構成は以下のようになります。

```
myapp
  |-- Dockerfile
  |-- docker-compose.yml
  |-- Gemfile
  |-- Gemfile.lock
```
それでは、先ほど作成したディレクトリの中に以下の各ファイルを作成していきます。

###① Gemfile

```ruby:Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 5.2.4', '>= 5.2.4.2'
```
③で作成する`Dockerfile` で `bundle install`する項目があるのですが、そのときにこの`Gemfile`を使います。
###② Gemfile.lock

```Gemfile.lock
```
`Gemfile.lock`の中身は空でOKです。
`Gemfile.lock` は `Gemfile`とセットで必要なので、用意します。

###③ Dockerfile

```dockerfile:Dockerfile
FROM ruby:2.5.7
# ベースにするイメージを指定

RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs default-mysql-client vim
# RailsのインストールやMySQLへの接続に必要なパッケージをインストール

RUN mkdir /myapp
# コンテナ内にmyappディレクトリを作成

WORKDIR /myapp
# 作成したmyappディレクトリを作業用ディレクトリとして設定

COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
# ローカルの Gemfile と Gemfile.lock をコンテナ内のmyapp配下にコピー

RUN bundle install
# コンテナ内にコピーした Gemfile の bundle install

COPY . /myapp
# ローカルのmyapp配下のファイルをコンテナ内のmyapp配下にコピー
```
`Dockerfile`は自分オリジナルの イメージ を作成するための設計書のようなものです。
今回は`ruby:2.5.7`という元々誰かが作ったイメージをベースに、Railsのインストールに必要なパッケージをインストールしたり、`myapp`をコピーしたり、`bundle install`したりと、様々なもの加えているというわけです。

###④ docker-compose.yml
```yml:docker-compose.yml
version: '3'
# docker-composeの書式のバージョンを指定します。(原則、最新を指定する)

services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_USER: user
      MYSQL_ROOT_PASSWORD: pass
    ports:
      - "3306:3306"
    volumes:
      - mysql_data:/var/lib/mysql

  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

volumes:
  mysql_data:
```
`docker-compose.yml`は複数のコンテナを定義し、実行するためのファイルです。
ファイルに書かれている単語をひとつずつ解説してきます。

###services:
`services`の中でコンテナを定義します。
今回は`MySQL` と `Rails`をコンテナ化するので、名前をわかりやすく`db`、`web`とします。
実際に作成されるコンテナ名は__「アプリケーション名 + サービス名 + 連番」__で`myapp_db_1`、`myapp_web_1`となります。

###image:
利用するイメージを指定し、コンテナを構築します。

###enviroment:
MySQLに関する環境変数を設定します。（今回はパスワードのみ）
パスワードはご自身で決めて構いません。

###ports:
ポート番号に関するを設定します。
MySQL は `3306`、 Rails は `3000` というポート番号がデフォルト値として設定されています。
Railsに関してはおなじみの`localhost:3000`の 3000 ですね。
Dockerを使わず、ローカルで環境構築した場合は直接この`localhost:3000`にアクセスすれば良かったわけですが、Dockerを使った場合、コンテナに外部からアクセスすることができないので、ちょっとした工夫が必要です。
そこで登場するのがこの`ports:`です。
`ports:` は `- ローカル側のポート番号 : コンテナ側のポート番号`で表します。
今回、`ports: - 3000:3000`としていますが、説明をわかりやすくするため、仮にもし`ports: - 9000:3000`に設定するとします。
これはコンテナ側の`3000ポート`に`9000ポート`でのアクセスを許可するという意味になります。
つまりこの場合、`localhost:9000`でアクセス可能になるということになります。

###volumes:
ボリュームに関する設定をします。
`ボリューム`とはコンテナにおいて生成されるデータを永続的に保存するためのものです。
MySQLを例にとると、MySQLをコンテナ化し、そこに テーブル や カラム などのデータを保存したとします。
しかし、そのMySQLのコンテナを削除してしまうと、保存してあったテーブル、カラムなどのデータも一緒に削除されてしまいます。
それはまずいので、データだけローカルにも保存しておく、この仕組みを`ボリューム`といいます。
このボリュームを利用すると、仮にコンテナを削除したとしても、新しくコンテナを立ち上げたときに、そのデータを再利用することができます。

ボリュームを保存する方法は大きく分けて2つあります。
１つ目は、__保存したいディレクトリをマウントする方法__、２つ目は、__名前付きボリュームを利用する方法__ です。

今回`MySQL`は２つ目の`名前付きボリュームを利用する方法`を使います。

この方法は、ローカルに名前付きのボリュームを作成し、そこにコンテナ側の指定したディレクトリを保存するというものです。
名前付きボリュームの場合、`volumes:` は `- ボリューム名 : コンテナ側の保存させたいディレクトリ`で表します。
今回、`ボリューム名` を `mysql_data`とし、`コンテナ側の保存させたいディレクトリ`は MySQLのデータ保存場所である`/var/lib/mysql`を指定します。
結果、`volumes: - mysql_data:/var/lib/mysql`という書き方になります。
そして、version: や services: と同じ段落(トップレベル)の、一番下に書いている`volumes:`にボリューム名を記載し、名前付きボリュームであることを明示します。

次に`Rails`は、１つ目の`保存したいディレクトリをマウントする方法`を使います。
この方法は、「ローカル側の指定したディレクトリ」と「コンテナ側の指定したディレクトリ」を同期させて、両者を常に同じ状態に保つというものです。
マウントする場合、`volumes:` は `- ローカル側の同期させたいディレクトリ : コンテナ側の同期させたいディレクトリ`で表します。
今回、`volumes: - .:/myapp/`としています。
つまり、`.`( docker-compose.ymlのあるディレクトリ、つまりローカル側の`myapp配下` ) と `/myapp/`( コンテナ側の`myapp配下` ) を同期するということです。
これにより、コンテナ起動後、ローカルのmyapp配下のファイルなどを編集すると、コンテナ側にも編集が反映されます。

###build:
Dockerfileのあるディレクトリをして指定します。( docker-compose.ymlから見て同じディレクトリにDockerfileが存在するので`.`)
ここのステップで ③で説明した`Dockerfile`を利用しオリジナルのイメージを作成し、コンテナを構築します。

###command:
コンテナ起動時の実行されるコマンドを設定します。
今回設定するコマンドは、`bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"` です。
`bash -c ""`は コンテナの中に入り`""`内のコマンドを実行するという命令です。
`rm -f tmp/pids/server.pid`で rails server が起動していた場合、停止します。( 何らかの原因でrails server がすでに起動していた場合、新しいrails serverが起動できないため。)
`bundle exec rails s -p 3000 -b '0.0.0.0'`で rails server を起動します。
`&&`は複数のコマンドを実行するときに用います。

###depends_on:
コンテナの作成順序の設定です。
`depends_on: - db`は、`MySQLのコンテナ`が起動してから`Railsのコンテナ`を起動するという意味です。

## rails new を実行

```
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
```
__アプリケーションの作業ディレクリに移動し__、`docker-compose run`コマンドで`rails new`を実行します。

`--force` : 同じファイルがある場合、上書きする
`--database=mysql` : データベースにMySQLを指定する
`--skip-bundle` : bundle install をスキップする。( bundle installはあとで行います。)

このコマンドを実行すると、`Dockerfile`を元に、イメージとコンテナを作成し、そのコンテナの中で`rails new`を実行します。
よって、ローカルにも見慣れたファイルが作成されたと思います。

## docker-compose build を実行

```
$ docker-compose build
```
`rails new` で `Gemfile`が更新されたので、`bundle install`するため、`docker-compose build`を実行します。
先ほどの`docker-compose run`で作成されたイメージは`rails new`される前の`Gemfile`を`bundle install`したイメージなので、更新された`Gemfile`を`bundle install`したイメージを再度作成する必要があります。

## database.yml を編集

```yml:/config/database.yml
# MySQL. Versions 5.1.10 and up are supported.
#
# Install the MySQL driver
#   gem install mysql2
#
# Ensure the MySQL gem is defined in your Gemfile
#   gem 'mysql2'
#
# And be sure to use new-style password hashing:
#   https://dev.mysql.com/doc/refman/5.7/en/password-hashing.html
#
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
# 以下２行を編集
  password: pass
  host: db

development:
  <<: *default
  database: myapp_development

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
  <<: *default
  database: myapp_test

# As with config/secrets.yml, you never want to store sensitive information,
# like your database password, in your source code. If your source code is
# ever seen by anyone, they now have access to your database.
#
# Instead, provide the password as a unix environment variable when you boot
# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
# for a full rundown on how to provide these environment variables in a
# production deployment.
#
# On Heroku and other platform providers, you may have a full connection URL
# available as an environment variable. For example:
#
#   DATABASE_URL="mysql2://myuser:mypass@localhost/somedatabase"
#
# You can use this database configuration with:
#
#   production:
#     url: <%= ENV['DATABASE_URL'] %>
#
production:
  <<: *default
  database: myapp_production
  username: myapp
  password: <%= ENV['MYAPP_DATABASE_PASSWORD'] %>
```
`password:` と `host:`を編集します。
`password:` には `docker-compose.yml` の `MYSQL_ROOT_PASSWORD:`に書いたパスワードを記載します。
`host:` には `docker-compose.yml`で命名したMySQLのコンテナ名`db`を記載します。

## docker-compose up -d でコンテナを起動

```
$ docker-compose up -d
```
このコマンドでコンテナを起動します。
`-d`をつけることでバックグラウンドで起動します。

これでコンテナが起動できました。
以下のコマンドでちゃんと起動しているか確認しましょう。

```
$ docker-compose ps

   Name                  Command               State                 Ports              
----------------------------------------------------------------------------------------
myapp_db_1    docker-entrypoint.sh mysqld      Up      0.0.0.0:3306->3306/tcp, 33060/tcp
myapp_web_1   bash -c rm -f tmp/pids/ser ...   Up      0.0.0.0:3000->3000/tcp 
```
このように、それぞれのコンテナが `UP` になっていれば起動成功です。

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

```
$ docker-compose exec web rails db:create
```

## localhost:3000にアクセス
`localhost:3000`にアクセスし、以下のページが表示されれば DockerによるRailsの環境構築 は完了です。
![スクリーンショット 2021-01-20 19.39.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/654387/9dea6e13-cc67-04f3-5eef-65137ddff222.png)

## 環境構築後の開発の仕方
これまで通り、__ファイルの編集などは基本ローカル環境で行います__。
しかし`rails g` や `rails db:migrate`などの __railsコマンド__ はコンテナ内で実行します。
なので、コンテナに入る必要があります。
以下のコマンドでRailsのコンテナに入ります。

```
$ docker-compose exec web bash
```
するとプロンプト( ターミナルの左側の部分 )が以下のように変わると思います。( @の後ろはコンテナのID )

```
root@97b8e3430f3f:/myapp#
```

これでコンテナの中に入れました。ここで railsコマンド を実行してください。
これまでのローカル環境での開発で、ターミナルで実行していたコマンドは__全てこのコンテナ内で行います__。( gitコマンドはローカル環境で行います )

コンテナを抜けるには、以下のコマンドを実行してください。( もしくは`control + d` )

```
$ exit
```

###- ログの表示 -
これまでは`rails s`でサーバーを起動し、ログが表示されていたと思います。
コンテナ内において、ログを表示させる方法は以下の通りです。

__コンテナ内に入り__、以下のコマンドを実行します。

```
$ tail -f log/development.log
```
これでログが表示されます。

ターミナルでタブを３つ開き、__「ローカルの操作をするタブ」__、__「コンテナ内でrailsコマンドなどを実行するタブ」__、__「ログを表示するタブ」__に分けると効率的な開発ができるかもしれません。

###- デバッグに関して -

開発中にエラーが発生し、controllerを編集し、デバッグしたとします。
この場合、一度サーバーを再起動させると思います。

Dockerの場合、Railsのコンテナ( myapp_web_1 )ごと以下のコマンドで再起動します。

```
$ docker-compose restart web
```

しかし、デバッグするたびにいちいちコンテナを再起動するのは非効率です。
なので、サーバー起動中もコードの更新をチェックしてくれるように設定します。

`config/environments/development.rb`の一番下に記載されている以下を

```rb
config.file_watcher = ActiveSupport::EventedFileUpdateChecker
```
次のように変更します。

```rb
config.file_watcher = ActiveSupport::FileUpdateChecker
```

これで再起動を行わずにデバッグが可能になります。

## その他の基本的なDockerのコマンド
####・ コンテナの停止、削除

```
$ docker-compose down
```

####・ コンテナの停止、削除および紐づいている名前付きボリュームの削除

```
$ docker-compose down -v
```
__※ 永続化しているデータが消えるの要注意 !__

####・ docker-composeのログを出力

```
$ docker-compose logs
```
コンテナがうまく起動しなかったときなどにログを見ると、原因がわかったりします。

####・ イメージの一覧

```
$ docker images
```

####・ イメージの削除

```
$ docker rmi イメージID
```

####・ 名前付きボリュームの一覧

```
$ docker volume ls
```

####・ 名前付きボリュームの削除

```
$ docker volume rm ボリュームの名前
```

## まとめ
最初の内は理解できないことも多いと思います。
ひとつずつ着実に理解していくのではなく、全体的に漠然と理解していくと良いと思います。

__この記事を読んでわからないことがあれば、コメントしていただければお答えいたします !__

最後まで読んでいただきありがとうございました。

## 参考
- https://kitsune.blog/docker-summary