Ruby on Rails  「途中まで作ったアプリにDockerを導入したい」に挑戦してみる（MySQL / Sequel Pro)

### はじめに
[Dockerの公式Tutorial](https://docs.docker.com/compose/rails/)では、DB:PostgreSQLで解説されている為、他のDB（例：MySQL)でDockerを導入してみたい初学者にとってはややハードルが高い状況です。

本記事では、途中まで作ったRailsアプリ（DB：MySQL)にDockerを導入する方法を解説します。

Dockerを途中から導入するアプリの例としては、scaffoldで作成した
簡単な蔵書管理アプリ(bookapp)を使用します。
(適宜、ご自身で開発中のアプリに置き換えて頂ければと思います）

DBはMySQL5.7で実装を進めます。

併せてDocker環境でもDB内容を視覚的に確認できるように
SequelProとの接続方法も解説します。

### 前提
- macでDockerが既に導入されている  
 Dockerインストールはこちらの記事が分かりやすいです。  
 [DockerをMacにインストールする](https://qiita.com/kurkuru/items/127fa99ef5b2f0288b81)
- Sequelpro インストール済  
 Sequelproの解説記事はこちら  
 [データベースの操作がGUIで分かりやすく操作できるSequel Proの最低限の使い方](https://webrandum.net/sequel-pro/)

### 目指すゴール

#### (1) 既存のRailsアプリにMySQLを使ったDocker環境を構築する

#### (2) Sequelproと接続し、Docker環境でもGUIでDB内容を確認できるようにする

### 実装の流れ
1 Dockerfile/docker-compose.ymlを作成

2 Dockerfileを編集

3 docker-compose.ymlを編集

4 database.yml編集

5 コンテナをbuildする docker-compose build

6 Dockerコンテナ上でDB作成＆migrationを実行 
  　docker-compose run web bundle exec rake db:create / db:migrate

7 コンテナを起動する docker-compose up 

8 DB接続設定を環境変数に置き換え - env_file

9 Docker環境のRailsアプリとSequelProを接続する

### 1 Dockerfile/docker-compose.ymlを作成
まずは既存アプリに必要なファイルを追加します。
具体的には(1)Dockerfile (2) docker-compose.ymlの２ファイルです。

アプリのルートディレクトリに以下のように２つのファイルを追加してください。
（ファイルの作成場所に注意してください）

```
bookapp ----|-- app
            |-- bin
            |-- config
            |-- db
            ・・・・・・
　　　　　　　・・・・・・
            |-- Gemfile
            |-- Gemfile.lock
            |-- package.json
            |-- Rakefile
            |-- README.md
            |-- Dockerfile  #ここに追加
            |-- docker-compose.yml　#ここに追加          
```
[解説] 
**Gemfile/Gemfile.lockについて**
・アプリ開発当初からDockerを導入する記事では、ここでGemfile（railsのみ導入）/Gemfile.lock（空でOK）を作成するように記載されていますが、既に開発中のアプリで作成済かと思いますのでGemfileについては特に何もしなくてOKです。


### 2 Dockerfileを編集
次にDockerfileを以下のように編集します。

``` Docker:Dockerfile:
FROM ruby:2.5

RUN apt-get update -qq && \
    apt-get install -y build-essential \ 
                       libpq-dev \        
                       nodejs      

RUN mkdir /app_name 

ENV APP_ROOT /app_name 
WORKDIR $APP_ROOT

ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

RUN gem install bundler 
RUN bundle install
ADD . $APP_ROOT

```
[解説]
**RUN gem install bundlerについて**
Railsアプリに途中からDockerを導入する場合、Gemfileが既にある関係で上記のコマンド無しで進めようとするとエラーが出るかもしれません。（自分の環境ではBuildした時に以下のようなエラーが出ました）

```
ERROR: Service 'web' failed to build: The command '/bin/sh -c bundle install' returned a non-zero code: 20
```
対策として、DockerfileをBuildする際にbundlerを導入しておきます。

### 3 docker-compose.ymlを編集
続いて docker-compose.ymlを以下のように編集します。

```docker-compose.yml:
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: 'password'
    ports:
      - "4306:3306"

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    depends_on:
      - db
```

[解説]
**1 db:portsの設定について- "4306:3306"**
DockerコンテナとSequelpro接続の為に必要な設定です。（詳細は後ほど）
とりあえず現段階では、上記の設定にしておいてください。


**2 rm -f tmp/pids/server.pidについて**
開発途中に何度もdockerコンテナの起動＆停止を繰り返していると時々「A server is already running.」のエラーが出ます。(pidファイルが残っていることが原因）
上記コマンドでserver.pidを逐一削除することで未然にエラーを防ぎます。

関連記事
[docker-compose upしたときに「A server is already running.」って言われないようにする](https://qiita.com/paranishian/items/862ce4de104992df48e1)

### 4 database.yml編集
次にconfig/database.ymlの設定をdocker-compose.ymlの内容に合わせます。
具体的には(1) password (2)host:dbの２つを追記してください。

```config/database.yml:

default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password
  socket: /tmp/mysql.sock
  host: db 　

development:
  <<: *default
  database: bookapp_development

```


### 5 コンテナをbuildする docker-compose build
ここまで来たら、次はDocker環境のコンテナを作っていきます。
ターミナルで以下のコマンドを実行してください。
(初めて実行するときは少し時間がかかります）

```terminal
[bookapp] $ docker-compose build 
```

### 6 Dockerコンテナ上でDB作成＆migrationを実行 
buildが無事完了したら、次はDB作成＆migrationです。
ターミナルで以下のコマンドを実行してください。

```
[bookapp] $ docker-compose run web bundle exec rake db:create
Created database 'bookapp_development'
Created database 'bookapp_test'

[bookapp] $ docker-compose run web bundle exec rake db:migrate

== CreateBooks: migrating ======================================
-- create_table(:books)
   -> 0.0150s
== CreateBooks: migrated (0.0152s) =============================
```
[解説]
Dockerコンテナはlocalとは環境が異なります。
その為、再度DB作成＆migrationの実行が必要になります

**docker-compose run webコマンドについて**
Dockerコンテナでのrailsコマンドは通常のコマンドの前に
「docker-compose run web」をつけて実行する必要があります。
通常のlocal環境のように「rake db:migrate」のみでは
変更が反映されないので注意してください。

### 7 コンテナを起動する docker-compose up 
ここまで来たら、ブラウザでTopページ表示が可能になります。

まず以下のコマンドでDockerコンテナを立ち上げます。

```
[bookapp] $ docker-compose up 
Starting bookapp_db_1 ... done
Recreating bookapp_web_1 ... done
```
コンテナが立ち上がった後、ブラウザでTopページが表示されるか確認してみてください。
http://localhost:3000/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481666/08f5225d-1100-6395-156e-3c1a6d83b7d9.png)

### 8 DB接続設定を環境変数に置き換え - env_file
ここまでの作業でDocker開発環境の構築は完了ですが、
DB接続関連の設定がファイルにベタ打ち（ハードコーディング）になっているのが
気になる方もいらっしゃるかもしれません。

Docker開発でも環境変数でDB接続関連の設定を読み込むことが可能です。

具体的には以下3つの作業を実施します。

(1) envファイル(db.env)を作成 
(2) database.ymlを編集
(3) docker-compose.ymlを編集

#### 1)envファイル(db.env)を作成
Dockerfileと同じルートディレクトリにenvファイルを作成します。
本記事ではファイル名を「db.env」とします。
以下ファイルを追加してください。

```db.env:
DB_USERNAME=root
DB_PASS=password
DB_HOST=db
MYSQL_ROOT_PASS=password
```

#### 2)database.ymlを編集
上記で追加した環境変数を読み込む為、config/database.ymlを編集します。

```database.yml:
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('DB_USERNAME', 'root') %>
  password: <%= ENV.fetch('DB_PASSWORD', 'pass') %>
  socket: /tmp/mysql.sock
  host: <%= ENV.fetch('DB_HOST', 'db') %>

development:
  <<: *default
  database: bookapp_development

```

[解説]
**ENV.fetch(第一引数, 第二引数)について**
第一引数に「環境変数」、第二引数に「デフォルト値」をセットします。
環境変数がnullの場合は第二引数のデフォルト値が読み込まれます。


#### 3)docker-compose.ymlを編集
仕上げにdocker-compose.ymlの設定を変更します。
db、webの２箇所にdb.envを読み込む設定（env_file）を追加してください。

```docker-compose.yml:
version: '3'
services:
  db:
    image: mysql:5.7
    ports:
      - "4306:3306"
    env_file: db.env

  web:
    build: .
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    depends_on:
      - db
    env_file: db.env

```

ここまで出来たら一度コンテナを停止＆再起動し、
環境変数経由でもTopページが表示できることを確認します。

```
# docker-compose stop or ctrl+Cでコンテナの停止が可能です。
Stopping bookapp_web_1 ... done
Stopping bookapp_db_1  ... done

#コンテナを再起動
[bookapp] $ docker-compose up
Starting bookapp_db_1 ... done
Starting bookapp_web_1 ... done
```

http://localhost:3000/

### 9 Docker環境のRailsアプリとSequelProを接続する
それでは最後にDockerコンテナとSequelProを接続してみましょう。

以下のSequelPro接続設定のページを開いてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481666/2ade5653-304c-e4e1-3acc-9d0a8792cb60.png)

#### Dockerコンテナ用の接続設定を追加する
設定ページが開けたら以下のようにDockerコンテナ用の接続設定を追加してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481666/38a5a3aa-79f9-bac6-6fc0-1473193c04a5.png)

「名前」・・・任意の名前でOKです。ここでは「docker-sql」としています。
「ホスト」・・・「127.0.0.1」
「ユーザー名/パスワード」・・・envファイルに合わせて記載してください。
「ポート」・・・ 「4306」で設定します。

[解説]
**ポート：4306について**
docker-compose.ymlのポート設定を思い出してください。

ports: "4306:3306"

これはホストが4306で接続した時にコンテナ上では3306に置き換えるという意味になります。　
localhostのMysqlのデフォルトのポート番号は3306です。
その為ポート番号：3306をそのまま使うと競合が発生して接続エラーが起こります。

エラー回避の為、上記のようにDocker環境ではポート：4306を使ってSeqelProに接続する設定にします。

上記設定が完了したら、早速確認してみましょう。
dockerコンテナが起動している状態でSequelProの「接続」をクリックします。

無事接続できていれば以下のようにDocker環境でもGUIでテーブル内容を確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/481666/ef7eeaf9-27cc-8996-1251-1fb6718f33ba.png)

### 参考記事
[Docker 公式Tutorial](https://docs.docker.com/compose/rails/)
[丁寧すぎるDocker-composeによるrails5 + MySQL on Dockerの環境構築(Docker for Mac)](https://qiita.com/azul915/items/5b7063cbc80192343fc0#%EF%BC%93gemfile%E3%82%92%E4%BD%9C%E6%88%90%E7%B7%A8%E9%9B%86)
[既存のRailsアプリにDockerを導入する手順](https://qiita.com/kenzoukenzou104809/items/b9e716204e0cd0cea447)
[Dockerで利用する環境変数をenv_fileを利用して一元管理する方法](https://nishinatoshiharu.com/dockercompose-envfile/)