【Docker】Rails圧倒的環境構築&Herokuへ簡単デプロイ

<a href="https://qiita.com/advent-calendar/2019/docker">Docker Advent Calendar 2019</a> の18日目。
どうもてぃです。
僕の好きな数字が **18** で、advent calendarを見たらちょうど空いてたので参戦いたしました。
しょーもない理由です。どうぞ、お手柔らかにおねがいします。

##楽に環境構築したい。。。

弊社では基本バックRails・フロントReactでやっているのですが、毎度環境構築するのにみんな正直愛想を尽かしている次第です。
本番のことはとりあえず置いておいて、ローカル開発環境くらいは一瞬で作ってしまって、さっさとプロジェクトにジョインしたい。
そういった考えの元から、オレオレDockerfile/docker-compose.ymlでRails & PostgreSQL環境を一発で作ってしまおうという結論にいたりました。

人間楽をしたい生き物ですから、 **退屈なことは自動化で瞬殺しましょう**。

##やりたいこと

* Rails new
* bundle install
* DB作成
* サーバー起動
* ブラウザ起動

以上を簡単なシェルスクリプトを流して実行します。

超絶簡単です。
猿でもできる環境構築。

##実行環境
以下検証当初バージョン

* elementary OS loki 0.4.1 (Ubuntu 16.04.5)
* Docker version 17.12.0-ce, build c97c6d6
* docker-compose version 1.20.1, build 5d8c71b

**※可能であればdocker-composeのバージョンは1.20以上にしておいてください。**

###docker & docker-compose最新版適応
* docker-compose version 1.25.1-rc1, build d92e9bee
* Docker version 19.03.5, build 633a0ea838

この記事を書いてる際にdockerとdocker-composeのバージョンを最新にしたところ問題なく動きました。
殆どmacユーザーだとは思うのですが、念の為バージョンアップの方法を書いておきます。
以下必要のない人は[git clone](#git-clone)まで飛ばしてください。

**※すんません、macについては未検証ですm(_ _)m**

###docker upgrade

```bash
# for mac
$ brew cask upgrade docker



# for linux(ubuntu)
$ sudo apt purge docker-ce

# インストール時にやっていれば飛ばしておｋ
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

# 同上
$ sudo add-apt-repository \
> "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
> xenial \
> stable"

$ sudo apt update; apt install docker-ce

$ docker -v
Docker version 19.03.5, build 633a0ea838
```

:tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: 

###docker-compose upgrade

mac/linuxどちらも以下の手順でいけるはず。
<a href="https://github.com/docker/compose/releases/">こちら</a>にアクセスし、自分の欲しいバージョンを見つけ、curl時にバージョンを指定してください。
今回は一番上にあった、 `1.25.1-rc1` を使用します。

```bash
# docker-composeの場所確認
$ which docker-compose
/usr/local/bin/docker-compose

# docker-compose削除
$ sudo rm /usr/local/bin/docker-compose

# docker-compose 1.25.1-rc1をダウンロード
$ sudo curl -L https://github.com/docker/compose/releases/download/1.25.1-rc1/docker-compose-$(uname -s)-$(uname -m) -o /usr/local/bin/docker-compose

# docker-composeコマンドをsudoなしで利用できるように
$ sudo chmod +x /usr/local/bin/docker-compose

$ docker-compose -v
docker-compose version 1.25.1-rc1, build d92e9bee
```

:tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: 

##git clone

ゼロから説明するのは今回の記事の趣旨に反するので、とりあえず<a href="https://github.com/motty93/docker-rails-postgresql">GitHub</a>にリポジトリを用意しました。
`README.md` 通りに実行すれば、サーバー起動まで上手くいくはずです（メモリが足りないとかなければ）

↓リンク
https://github.com/motty93/docker-rails-postgresql

（すんません、うまくいかなかったらissueあげてもらえると即対応します。）

##手順

`README.md`に手順書いてあるんですが、念の為こちらにも書いておきます。

```bash
$ git clone https://github.com/motty93/docker-rails-postgresql

$ cd docker-rails-postgresql/

$ sudo chmod 755 init.sh

$ ./init.sh
```

割と時間がかかるので、スクリプトを実行したら終わるまで大人しくtwitterやようつべを見てのんびりしておきましょう。

##自動化シェル
とはいいつつも、ただ実行するコマンドを羅列しただけです。
最終的には`docker-compose up -d`でサーバーをデーモン起動し、ブラウザを立ち上げます。

```bash
#!/bin/bash

echo "rails new"
docker-compose run web rails _5.2.4_ new . --skip --database=postgresql \
  --skip-git --skip-bundle --skip-turbolinks --skip-coffee --skip-test

if [ "$(uname)" == 'Linux' ]; then
  echo "Linux chown all file"
  sudo chown -R $USER:$USER *
fi

echo "force files checkout"
git checkout README.md .gitignore

echo "fig build"
docker-compose build

if [ "$(uname)" == 'Linux' ]; then
  echo "Linux chown all file"
  sudo chown -R $USER:$USER *
fi

echo "bundle install"
docker-compose run web bundle install --path=vendor/bundle

echo "copy env setting"
cp ./docker/init/env_sample ./.env

echo "copy database.yml"
\cp -f ./docker/init/init_database.yml ./config/database.yml

echo "db create & migrate"
docker-compose run web rails db:create
docker-compose run web rails db:migrate

echo "fig up deamon"
docker-compose up -d

echo "browser open"
if [ "$(uname)" == 'Darwin' ]; then
  open "http://0.0.0.0:3000"
else
  xdg-open "http://0.0.0.0:3000"
fi
```

(windowsは想定してなかったので自動化できてませんすんません)

##Dockerfile

```dockerfile
FROM ruby:2.6.3
ENV LANG C.UTF-8
ENV APP_ROOT /myapp
ENV DEBCONF_NOWARNINGS yes

RUN apt-get update -qq && apt-get install -y \
    build-essential \
    libpq-dev \
    postgresql-client \
    nodejs \
    && rm -rf /var/lib/apt/lists/* /var/cache/apt/archives/* /tmp/* /var/tmp/*

RUN gem install -v 5.2.4 rails \
  && gem install bundler

WORKDIR /tmp

COPY Gemfile* /tmp/
RUN bundle install --clean \
    && mkdir ${APP_ROOT}

WORKDIR ${APP_ROOT}
ADD . ${APP_ROOT}/

CMD ["rails", "server", "-b", "0.0.0.0"]
```

至って平凡なDockerfileです。
Herokuへデプロイする際、Dockerfileの最後に**サーバー起動コマンドがないとデプロイしてもアプリケーションエラーが発生してしまうため** `CMD ["rails", "server", "-b", "0.0.0.0"]` を追記しています。
デプロイする予定の無い方は外していただいて問題ないです。

##docker-compose.yml

```yml
version: '3'
services:
  postgres:
    image: postgres:10-alpine
    volumes:
      - postgresql-data:/var/lib/postgresql/data
      - ./docker/init/db:/docker-entrypoint-initdb.d
    environment:
      - POSTGRES_USER
      - POSTGRES_PASSWORD
      - POSTGRES_DB
      - POSTGRES_PRODUCTION_DB
    ports:
      - '5432'
  web:
    build:
      context: .
      dockerfile: ./docker/rails/Dockerfile
    working_dir: /myapp
    command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -b 0.0.0.0 -p 3000"
    ports:
      - '3000:3000'
    volumes:
      - .:/myapp
      - /myapp/log
      - /myapp/.git
    environment:
      - POSTGRES_HOST
      - POSTGRES_PORT
      - POSTGRES_USER
      - POSTGRES_PASSWORD
    depends_on:
      - postgres
    tty: true
    stdin_open: true
volumes:
  postgresql-data:
    driver: local
```

postgresqlのvolumeに関して結構問題がありました（Linuxだとdockerの権限周りめんどい）が、上記の構成で上手く自動化まで持っていけました。
特に目立ったところのない平凡な`docker-compose.yml`ですね。
今回、環境変数は`.env`を使用するため、`environment`のように指定すれば勝手に使ってくれます。超便利〜。

##初期設定関連ファイル

`docker/init`ディレクトリ配下に入っています。

`db/setup.sql`がpostgresqlの初期設定sql。
これがないと、実行ユーザーの権限がないやら、実行ユーザーのDBがないやら、postgresqlは遠慮なしに文句を行ってきます。
初期設定ユーザーの名前を変更したい場合、`init.sh`を実行する前に`setup.sql`の中身を変更してください。

`env_sample`は今回必要となる`.env`のサンプルファイル。
一応動くように環境変数を設定しています。
`setup.sql`のユーザー・パスワードを変更した場合、**こちらも変更しないとDBへ接続できないので注意**してください。

`init_database.yml`は環境変数に準じた設定をしています。
DB名を変更したい場合はrails new後、`config/database.yml`を変更したらいいんじゃないすか。

<br/>
以上、上記ファイル郡は`init.sh`が動いたときに適切な場面で適切な働きをしてくれます。
むしろ、最初以降は~~**使い物にならないタダのゴミ**~~使わないので、必要なければ初期スクリプト流した後削除してinitial commitしたらいいんじゃないすかね。

##Heroku container deploy

Railsといえば、おなじみのHerokuさんです。
ここ半年は検証本番含め結構お世話になってます。
`git push origin heroku`ってするのもいいんですが、せっかくDockerfile作ったんだから、コンテナデプロイしたくないですか？
てなわけで、公式を見てみましょう。

↓リンク
https://devcenter.heroku.com/articles/container-registry-and-runtime#getting-started

`Getting started`のところだけを順に実行したらデプロイ完了です。
###前準備

herokuへコンテナデプロイする際、アプリケーションルートにDockerfileがないとビルドしてくれません。
そのため、`docker/rails/Dockerfile`をアプリケーションルートへコピーします。

```bash
# docker/rails/Dockerfileをappルートへコピー
$ cp ./docker/rails/Dockerfile .
```

もう一点。
ローカルでコンテナが動いていると、heroku container:push後のアプリケーションクラッシュの一因になります。
念のため、コンテナを止めるか削除するかしておいてください。

僕は面倒くさがりなんでコンテナは毎回全削除してます。

```bash
$ docker rm -f `docker ps -a -q`

or

$ docker-compose down
```


###念のため手順

```bash
$ heroku login

$ heroku container:login

$ heroku create

$ heroku container:push web

$ heroku container:release web
```

これだけ。
クソ簡単でしょ？
たのしいHeroku第1版 :book: 

```bash
$ heroku open
```

![https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_139887_2e860399-ab7b-dfe1-3af8-063ab26fe9e0.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145671/fdad0bae-b2b0-4f48-32a9-c00d2ca897e5.jpeg)

＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／＼(^o^)／

## herokuへpostgresqlのアドオンをいれる

`heroku container:login`した後にpostgresqlのアドオンを入れましょう。
ただコンテナデプロイするだけじゃダメなんですよね。

```bash
$ heroku addons:create heroku-postgresql:hobby-dev

$ heroku open
```

これで簡単にデプロイができちゃいました。
パーフェクトHerokuContainer :book: 


###これでもダメだった場合

上記手順を踏んでも、`Application Error`が起きる場合`tmp/pids/server.pid`が残っている可能性が非常に高いです。
削除して、デプロイの手順をもう一度踏んでください。

以下手順。

```bash
$ rm tmp/pids/server.pid

$ heroku container:push web

$ heroku container:release web

$ heroku open
```

##Congratulation!!!

:tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: :tada: 

![2019-12-17 17.41.05 からのスクリーンショット.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/145671/dbfbe50e-546f-21f2-e772-aaadd9d6e0bb.png)


##記事を書くに当たり猛烈に躓いた点

###LoadError: cannot load such file aciton_mailbox/engine

以前作成したDockerfileとdocker-compose.ymlを使用していたので全く問題なくいくとおもっていたのですが、なぜか上記でハマりました。
しかも検索しても全然出てこない。

色々調査していると、コンテナ内のrailsのバージョンが`6.0.1`になっており、自前で準備した`Gemfile`のrailsと異なるバージョンになってしまっていました。

```bash
$ docker-compose run web rails -v #=> 5.2.4

$ docker run -it <image id> rails -v #=> 6.0.1

# は？
```
そりゃ、**バージョン6系でもないのにaction_mailboxとか入れようとしてる**からエラーになるわけですよね。
`config/application.rb`のrequireはずしてもダメでしたもん。

上記解決のため今回は`Dockerfile`で`gem install rails`する際にバージョン指定しています。
また、`init.sh`のスクリプトでrails newする際にもバージョンを指定しています。
この二点はすげぇ大事です。とてつもなくね。

##今回のポイント

* Dockerfileで`gem install rails`をする際バージョン指定する
* `docker-compose run web rails new`時もバージョン指定をする
* PostgreSQLの実行ユーザーをdbコンテナができる際に作成されるようsqlファイルを作っておく
* .env & database.ymlは予め作っておき、`db:create`が走る前に反映させる(自動化スクリプトで問題なくやってくれる)
* 権限でつまづかないようにpostgresql volumesの設定に気をつける（Linuxのみ）
* Gemfileは用意しておき、rails newの--skipオプションで上書きしないようにする

##まとめ

今後同じリポジトリでrails 6.0.1にも対応する予定です（随時更新予定）
是非、環境構築につかれた方、簡単な検証環境をherokuに作りたい方、試してみてはいかがでしょうか！？

もっと改善できそうなところあればpull requestください。
よろしくお願いします。

##参考

<a href="https://devcenter.heroku.com/articles/container-registry-and-runtime">Heroku dev center Container Registry</a>
<a href="https://qiita.com/asylum/items/17e655d8369c19affbc3">dockerでPostgreSQLのコンテナ作成と初期化</a>
<a href="https://qiita.com/haessal/items/0a83fe9fa1ac00ed5ee9">"debconf: delaying package configuration, since apt-utils is not installed"を表示しないようにする</a>
<a href="https://railsguides.jp/action_mailbox_basics.html">Action Mailbox の基礎</a>
<a href="https://devcenter.heroku.com/articles/heroku-postgresql">Heroku dev center Heroku PostgreSQL</a>