Railsでポートフォリオを作ってみよう！ vol.1 後編 (docker-compose導入〜rails構築編)

※[11月1日: 追記]
docker-compose build->up中に「Error response from daemon: OCI runtime create failed:」が発生したためそれの追記

前回、Docker導入を行ったが、今回はdocker-composeの導入を行う。
(前回のDocker導入編をまだご覧になっていない人は→[こちら](https://qiita.com/pondo/items/547a9cb4cd2a6f291468))

#docker-composeとは

Docker-composeとは、複数のコンテナをまとめて管理してくれるツールの名称。コンテナで作成した複数のサービスを構築・実行する手順を自動化し、管理を容易にしてくれる優れもの。
一つの専用ファイル(docker-compose.yml)を書いて、コマンドを叩くだけで使えるのも非常に便利。
Rails開発では複数のコンテナを使用する事が多々ある為Dockerを使うとなったら必須となってくる。
[日本語訳版公式文章](http://docs.docker.jp/compose/toc.html)

つまりは覚えて損はない。

#環境
今回の環境は以下の通り
・ruby 2.5.1
・rails 5.2.1 
・MySQL 5.7

#必要ファイルの確認
今回作成していくファイルは以下の4つ
・Dockerfile
・docker-compose.yml
・Gemfile
・Gemfile.lock

#構築
下記から構築を行っていくが、作業の区切り区切りは中タグで区切っている為、途中から作業を再開したい場合などは右の一覧から探すのがオススメ。
##作業ディレクトリの作成
作業ディレクトリの作成を行う。mkdirでファイルを作成しその中に入っていく。
今回「exampleApp」で作成を行っていく。

```
$ mkdir exampleApp
$ cd exampleApp
```

##Dockerfileの作成
まずDockerfileをexampleApp直下に置く

```
vimが使える場合
$ vi Dockerfile

vimが使えない場合
$ touch Dockerfile
を行いDockerfileを作成し、Atomなどで開いて編集しても良い
```

そしてDockerfileを編集する

```docker:exampleApp/Dockerfile
#rubyの指定、今回は環境欄で言った通り2.5.1を指定している。
FROM ruby:2.5.1

#shellの実行を行い、パッケージのインストールを行う。
RUN apt-get update -qq && \
    apt-get install -y build-essential \
                       libpq-dev \
                       nodejs

#RUN mkdirでファイルを作成し、ENVで環境変数の設定を行っている。
#WORDIDは起点ディレクトリの設定を行っている。
#ちなみに下記に書いてある[/app_name]の名前は変更しなくても良い
RUN mkdir /app_name
ENV APP_ROOT /app_name
WORKDIR $APP_ROOT

#Gemfileの作成
ADD ./Gemfile $APP_ROOT/Gemfile
ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock

#Gemfileのbundle installを走らせる
RUN bundle install
ADD . $APP_ROOT
```

編集を行ったら保存してDockerfileは終了。

##docker-compose.ymlの作成
Dockerfileと同じようにこちらもexampleApp直下に置く。

```
vimが使える場合
$ vi docker-compose.yml

vimが使えない場合
$ touch docker-compose.yml
を行いdocker-composeを作成し、Atomなどで開いて編集しても良い
```

こちらも開いて編集を行う。

```yml:examleApp/docker-compose.yml
#dbにある[MYSQL_ROOT_PASSWORD]のパスワードは後ほど使用する為覚えておく事
#インデントに注意する(インデントがずれている場合、エラーになる事が極めて高い)
#インデント表示無しVer
#versionは最新の3を使用
version: '3'
services:
  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: password
      MYSQL_DATABASE: root
    ports:
      - "3306:3306"

  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/app_name
    ports:
      - "3000:3000"
    links:
      - db

#インデント表示有りVer(こちらはインデント確認用として使用)
version: '3'
services:
..db:
....image: mysql:5.7
....environment:
......MYSQL_ROOT_PASSWORD: password
......MYSQL_DATABASE: root
....ports:
......- "3306:3306"

..web:
....build: .
....command: bundle exec rails s -p 3000 -b '0.0.0.0'
....volumes:
......- .:/app_name
....ports:
......- "3000:3000"
....links:
......- db
```

編集を行ったら保存してdocker-compose終了。

##Gemfileの作成
今回railsは5.2.1を利用

```
vimが使える場合
$ vi Gemfile

vimが使えない場合
$ touch Gemfile
```

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

編集して保存。
現状はrailsだけ入っていれば大丈夫。

##Gemfile.lockの作成
Gemfile.lockはただ単に作成だけ行う。
中の編集などは行わないので、

```
$ touch Gemfile.file
```

で完了。
これで必要なファイルの作成は終了。

##rails newを行う

docker-composeコマンドを使用し、rails newを実行する。

```
$ docker-compose run web rails new . --force --database=mysql --skip-bundle
```

##database.ymlの修正を行う

作成されたconfigファイルの中にあるdatabase.ymlの編集を行う。

```
vimが使える場合
$cd config
$vi database.yml

vimが使えない場合は直接configファイルに行き編集を行う。
exampleApp/config/database.yml
```

```yml:database.yml
default: &default
  adapter: mysql2
  encoding: utf8
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: root
  password: password # MYSQL_ROOT_PASSWORDを記入
  host: db # docker-compose.ymlのservice名を記入
```

保存する。

##docker-composeを起動する

ターミナルを確認。もしこの時点で

```
$ PC名: config $  
```

になっていたら [cd] を使用しexampleAppまで戻っておく

```
$ PC名: config $ cd ..
$ PC名: exampleApp $
```

コンテナを建てる

```
# コンテナをビルド
$ docker-compose build

# コンテナの作成＆起動(別ターミナルが必要になる)
$ docker-compose up 
# コンテナの作成＆起動（バックグラウンド）
$ docker-compose up -d
```

起動しおわったら [localhost:3000](localhost:3000)に飛びいつものページ(Yay! You're on Rails!)が出ていることを確認して完了。

##その他
####サーバーの終了方法
いつものctrl + cだけでは、railsサーバーは閉じたけどdb(mysql)サーバーは閉じていないなどの不具合が発生する事があるため、以下のいずれかの方法で終了させる。

```
コンテナを停止だけさせたい場合
$ docker-compose stop

コンテナを停止させる + 削除する
$ docker-compose down
```

もしミスってctrl + cを行ってしまった場合はtmp/pids/server.pidファイルを削除し再度起動を行う。
そんなファイルないやんって時は、一度docker-composeを起動し、その状態で再度ファイルを探して削除を行い再起動すると治る場合がある。

####なぜ2つの停止方法があるのか
他の理由もあるとは思うが、コンテナがある状態で新しくgemをインストールすると、サーバーの再起動を行っても反映されない事象がある。
原因としては簡単でDockerfileを見ればわかるように

```
RUN bundle install
```

を行っている。
docker-composeをstopさせただけでは以前のコンテナが残りっぱなしの為、docker-compose upを行ってもgemが対応しない。
なので、一回一回コンテナを削除して読み込ませないといけない。

そのため

```
gemは入れないけどとりあえず停止だけさせたい
→ $ docker-compose stop

gem新しく入れます、再度コンテナを作り直す理由が他にもあります
→ $ docker-compose down
```

と分けて使用する。

####DBでもエラーが出た場合

docker-compose down を行い build したのはいいけど、dbが無いよなどのエラーが発生する場合がある。
その時は、

```
docker-compose run web rails db:create
```

を行う。

####ちなみに
先ほど実行したdocker-compose run、 
docker-composeではよく使うので覚えた方が吉。

```
docker-compose run [docker-compose.ymlで設定したサービス名] (使用したいコマンド)
→ docker-compose run web rails db:create

ローカルでbundle installしたい
docker-compose run web bundle install 

gitコマンド使いたい
docker-compose run web git add .

RSpec使いたい
docker-compose run web bin/rspec
```

##追記
新しいgemを入れるためにdocker-composeをdownさせてから再度build->upさせた際、以下のようなエラーが発生した。

```
Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "exec: \"db:create\": executable file not found in $PATH": unknown
```

このエラーはよく見ることがあり、OCI runtime create failed errorとして分類されている。
複数のケースがあるため一概にもこの方法を行えば修正が効くとは言えないが、今回のケースでは

```
starting container process caused "exec: \"db:create\"
```

がエラーの原因。
なのでそこを修正していく。

##修正

```
１、新しくdbを作成する
docker-compose run web rails db:create

２、それをmigrateする
docker-compose run web rails db:migrate
```

以上。