DockerでRails+Webpackerの開発環境を構築するテンプレート


## 追記(2018/07/07)

`node` バイナリと `yarn` バイナリのインストールを、`node`の docker image からコピーする方式に改善しました。

参考URL: https://qiita.com/izumin5210/items/cf14a1ea58fb82d36bb2

## はじめに

docker-composeを使い、railsを簡単に開発できるテンプレートを作成しました。
webpackerの利用にも対応しています。

特徴は以下の通りです。

- `bundle install`をdockerイメージビルドの時でなく、ENTRYPOINTで行います。
	- インストールされたgemがDockerVolumeにキャッシュされるので、`Gemfile`を更新してもイメージの再ビルドをする必要がありません。
- `webpacker`にも対応。`webpack-dev-server`が自動で起動されます。

webフロントエンドは違うリポジトリで作るという考え方もありますが、このテンプレートを使うと、`heroku`にそのままアップロードすることで、フロントエンド、バックエンド双方が一つのサーバーで供給されるようになり、運用が楽になります。

## 前回との違い

1年前に[高速に開発できる Docker + Rails開発環境のテンプレートを作った](https://qiita.com/kawasin73/items/2253523be18e5afd994f)で、Docker + railsのテンプレートを紹介しましたが、以下の点が変わっています。

- webpackerに対応
	- webpackerを動かせるように、Node8の環境とyarnがインストールされています。
	- `webpack-dev-server`が自動で起動されます。
- `spring`コンテナの廃止
	- `spring`コンテナがあることで高速に各種コマンドを実行できていましたが、`rails`コンテナで十分だということに気づいたため、廃止しました。
	- `spring`コンテナと`rails`コンテナを同時に起動すると、`bundle install`が同時に実行され同じ、bundle pathを共有してみているためエラーが起こることがありました。

## 公開

ソースコードは、[kawasin73/rails_docker_template](https://github.com/kawasin73/rails_docker_template)で公開しています。

ブランチごとに環境を分けました。
`base/ruby-x.x.x-rails-x.x.x`は、テンプレートの状態のブランチです。各自の環境でアプリケーションのビルドを行う必要があります。
`base`ブランチには、全てまとめて、`Initial Commit`の１コミットしか含まれないようにしました。

```bash
git clone https://github.com/kawasin73/rails_docker_template.git .
git checkout origin/base/ruby-2.5.1-rails-5.2.0
git branch -d master && git checkout -b master
script/init && script/bootstrap

docker-compose exec rails bash
# access to http://localhost:3000
```

`ruby-x.x.x-rails-x.x.x`は、ビルド済みのテンプレートです。`credentials.yml.enc`を削除した状態でリポジトリにおいてあるので、credentials情報を初期化してから使う必要があります。

```bash
git clone https://github.com/kawasin73/rails_docker_template.git .
git checkout base/ruby-2.5.1-rails-5.2.0
git branch -d master && git checkout -b master
# initialize credentials.yml.enc
docker-compose run --rm rails bin/rails credentials:edit
script/bootstrap

docker-compose exec rails bash
# access to http://localhost:3000
```

現在(2018/04/14)公開しているブランチは以下の通りです。

- master
	- 標準テンプレートの開発ブランチ
- webpacker
	- webpacker環境の開発ブランチ
- [base/ruby-2.5.1-rails-5.2.0](https://github.com/kawasin73/rails_docker_template/tree/base/ruby-2.5.1-rails-5.2.0)
	- Ruby 2.5.1, Rails 5.2.0のテンプレートです。webpacker環境は含まれていません。
- [base/ruby-2.5.1-rails-5.2.0-webpack](https://github.com/kawasin73/rails_docker_template/tree/base/ruby-2.5.1-rails-5.2.0-webpack)
	- Ruby 2.5.1, Rails 5.2.0のwebpackerを使う時のテンプレートです。
- [ruby-2.5.1-rails-5.2.0](https://github.com/kawasin73/rails_docker_template/tree/ruby-2.5.1-rails-5.2.0)
	- Ruby 2.5.1, Rails 5.2.0のビルド済みアプリケーションです。
- [ruby-2.5.1-rails-5.2.0-webpack](https://github.com/kawasin73/rails_docker_template/tree/ruby-2.5.1-rails-5.2.0-webpack)
	- Ruby 2.5.1, Rails 5.2.0のwebpacker用のビルド済みアプリケーションです。

僕が必要になったら随時追加されていくと思います。

## テンプレートの中身

[base/ruby-2.5.1-rails-5.2.0-webpack](https://github.com/kawasin73/rails_docker_template/tree/base/ruby-2.5.1-rails-5.2.0-webpack)を解説していきます。
前回の[高速に開発できる Docker + Rails開発環境のテンプレートを作った](https://qiita.com/kawasin73/items/2253523be18e5afd994f)と重複している部分の解説は元記事からコピーします。

- `Dockerfile.dev`
- `docker-compose.yml`
- `Gemfile`
- `.env.dev.sample`
- `template/database.yml`
- `.gitignore`
- `script/init`
- `script/bootstrap`

### Dockerfile.dev

```docker:Dockerfile.dev
FROM node:10.6.0 as node
FROM ruby:2.5.1

ENV LANG C.UTF-8

RUN apt-get update -qq && \
    apt-get install -y --no-install-recommends \
    build-essential \
    libpq-dev \
    libfontconfig1 && \
    rm -rf /var/lib/apt/lists/*

ENV ENTRYKIT_VERSION 0.4.0

RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
    && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
    && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
    && mv entrykit /bin/entrykit \
    && chmod +x /bin/entrykit \
    && entrykit --symlink

ENV YARN_VERSION 1.7.0

COPY --from=node /opt/yarn-v$YARN_VERSION /opt/yarn
COPY --from=node /usr/local/bin/node /usr/local/bin/

RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
    && ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

RUN mkdir /app

WORKDIR /app

RUN bundle config build.nokogiri --use-system-libraries

ENTRYPOINT [ \
    "prehook", "ruby -v", "--", \
    "prehook", "bundle install -j3 --quiet", "--"]
```

オフィシャルの`node`イメージから`node`バイナリと`yarn`バイナリをコピーしています。

ファイル名はあえて「Dockerfile.dev」にしています。開発環境と本番環境では`Dockerfile`の中身は変わってくると思うので、`Dockerfile`は別途作って本番環境用に使うことができます。

`ENV LANG C.UTF-8` によって `rails console`の中で日本語の入力をできるようにしています。

最大の特徴は、`ENTRYKIT`だと思います。[開発しやすいRails on Docker環境の作り方](http://qiita.com/joker1007/items/9f54e763ae640f757cfb)を参考にしました。
`ENTRYPOINT` の中で `bundle install` を行うことでインストールした gem をDocker Volumeの中にキャッシュすることができます。
`Dockerfile`の中で`bundle install`をしないので、新しくgemを追加した時に、Dockerイメージを再度ビルドする必要がなく、高速に開発を進めることができます。

開発環境では、ローカルのrailsディレクトリを、Dockerコンテナにマウントして開発します。
そのため、`Dockerfile.dev`では、`COPY . /app`をしません。`RUN mkdir /app` で`/app`ディレクトリを作成するだけにとどめます。


### docker-compose.yml

```yaml:docker-compose.yml
version: '2'
services:
  rails: &app_base
    build:
      context: .
      dockerfile: "Dockerfile.dev"
    command: ["bundle", "exec", "rails", "s", "-p", "3000", "-b", "0.0.0.0"]
    env_file:
      - "./.env.dev"
    volumes:
      - ".:/app"
    volumes_from:
      - data
    ports:
      - "3000:3000"
    depends_on:
      - db
    tty: true
    stdin_open: true
  webpack:
    <<: *app_base
    command: "bin/webpack-dev-server"
    ports:
      - "3035:3035"
    depends_on:
      - rails
    tty: false
    stdin_open: false
  db:
    image: "postgres:10.3"
    environment:
      - "POSTGRES_USER=postgres"
      - "POSTGRES_PASSWORD=password"
    volumes_from:
      - data
    ports:
      - "5432:5432"
  data:
    image: "busybox"
    volumes:
      - "db:/var/lib/postgresql/data"
      - "bundle:/usr/local/bundle"

volumes:
  db:
    driver: local
  bundle:
    driver: local
```

`webpack`コンテナが追加されました。

コマンドは、

```bash
docker-compose up -d
docker-compose exec rails bin/rails db:migrate
```

のように実行できます。

### Gemfile

```rb:Gemfile
# frozen_string_literal: true
# A sample Gemfile
source 'https://rubygems.org'
ruby '2.5.1'

gem 'rails', '5.2.0'

```

最初の状態では、`gem 'rails', '5.2.0'`のみを書きます。後述の`script/init`で、`rails new`することで`Gemfile`の中身を増やしてくれます。
また、`Gemfile.lock`はこの時点では必要ありません。

### `.env.dev.sample`

```:.env.dev.sample
DATABASE_HOST=db
DATABASE_PORT=5432
DATABASE_USER=postgres
DATABASE_PASSWORD=password
EDITOR=vim
```

データベースの情報は環境変数で railsアプリケーションに渡します。

```bash
cp .env.dev.sample .env.dev
```

を実行して利用してください。
`EDITOR=vim`は、Rails5.2から導入された`rails credentials:edit`のエディタを指定します。

### template/database.yml

```yaml:template/database.yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
  username: <%= ENV.fetch('DATABASE_USER') { 'root' } %>
  password: <%= ENV.fetch('DATABASE_PASSWORD') { 'password' } %>
  host: <%= ENV.fetch('DATABASE_HOST') { 'localhost' } %>
  port: <%= ENV.fetch('DATABASE_PORT') { 5432 } %>

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

production:
  <<: *default
  database: app_production

```

データベースの情報は環境変数で railsアプリケーションに渡します。

```bash
cp -f template/database.yml config/database.yml
```

で、自動生成される`database.yml`を上書きします。

### .gitignore

```:.gitignore
/log
/tmp
!/log/.keep
!/tmp/.keep
/public/system
/public/packs
/node_modules

.env
.env.dev

/.bundle
/vendor/bundle

/.idea

.DS_Store

```

そのままです。

### script/init

```bash:script/init
#!/usr/bin/env bash

cp .env.dev.sample .env.dev

docker-compose build

docker-compose run --rm rails bundle exec rails new . --force --database=postgresql --skip-turbolinks --skip-git --skip-test --webpack=react

cp -f template/database.yml config/database.yml
sed -i ".bak" -e "s/host: localhost/host: webpack/g" config/webpacker.yml

docker-compose run --rm rails bundle exec spring binstub --all

```

`webpack-dev-server`が起動するdockerコンテナのIPは、`localhost`ではないです。

```bash
sed -i ".bak" -e "s/host: localhost/host: webpack/g" config/webpacker.yml
```

`development.dev_server.host`には、`webpack-dev-server`を稼働させてるコンテナのDockerネットワーク内でのホスト名`webpack`を設定します。
`0.0.0.0`を設定しても、`webpacker-dev-server`は稼働しますが、HMRが正しく動かなくなるため、ホスト名を指定します。
一方、`Docker for Mac`では、IPアドレスはホストマシンから`127.0.0.1`として見えます。そのため、`development.dev_server.public`は、デフォルトの`localhost:3035`のまま変更しません。

### script/bootstrap

```bash:script/bootstrap
#!/usr/bin/env bash

cp .env.dev.sample .env.dev
docker-compose run --rm rails rails db:create
docker-compose run --rm rails rails db:migrate
docker-compose run --rm rails bin/yarn install
docker-compose up -d
```

`script/init`を実行して作成済みのプロジェクトを、`git clone`してきた後などに実行します。データベースの作成ができます。

## `binding.pry` を使う

`docker-compose.yml` で

```yaml:docker-compose.yml
    tty: true
    stdin_open: true
```

を設定しています。これによって、pumaのサーバーの処理に挿入した`binding.pry` を使えるようになります。

```bash
docker ps
```

コマンドで、railsコンテナのコンテナ名を調べてください。

```bash
docker attach xxxx_rails_1 # xxxx_rails_1 は調べたコンテナ名
```

とすると、binding.pryをDockerの中で使えるようになります。

## 参考URL

- [Docker で Rails5 の開発環境を「rails new」から構築する](http://qiita.com/kawasin73/items/d5836007c715b7f7885f)
- [開発しやすいRails on Docker環境の作り方](http://qiita.com/joker1007/items/9f54e763ae640f757cfb)
- [高速に開発できる Docker + Rails開発環境のテンプレートを作った](https://qiita.com/kawasin73/items/2253523be18e5afd994f)