Docker Compose でローカルの Rails 開発環境を作る

ローカルの Rails の開発環境を直接OSに構築してしまうと、
仕事で別のバージョンの DB が必要になったケースなどで非常に苦労するので
何らかの仮想マシン上に開発環境を構築するほうが楽だと思う。

Vagrant と何かしらのプロビジョニングツールを使う方法がベターだが、
Docker Compose を試しに使ってみたら便利だった。

## Docker Compose なに?

Docker における複数コンテナの管理を docker-compose.yml という1ファイルでできるようにするツールです。

詳しくは https://docs.docker.com/compose

### Vagrant と比べたメリット

- 設定ファイルが短い。

例えば Vagrant + ansible の場合、Ruby とか PostgreSQL とかのインストール方法を自分で書く必要がありますが、
docker-compose ならコンテナを取得するだけでいいのですごく楽です。

### デメリット

- 資料すくない
- ~~Mac だとやっぱり不安定な感じ。Linux使おう。~~ 最近はmac上のdocker-machineでも元気に動きます

## Rails の開発環境

https://docs.docker.com/compose/rails/ に Rails の開発環境の作り方があるので、これをベースにします。
次のことをやります。

- Node.js と phantom.js をいれる
- Rails console 上で日本語入力できるように `ENV LANG C.UTF-8` を指定
- bundle install した gem をキャッシュする

### Dockerfile

```
FROM ruby:2.2.3
ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs npm nodejs-legacy
RUN npm install -g phantomjs-prebuilt
RUN gem install bundler

ENV APP_HOME /myapp
RUN mkdir $APP_HOME
WORKDIR $APP_HOME

ADD Gemfile* $APP_HOME/

ENV BUNDLE_GEMFILE=$APP_HOME/Gemfile \
  BUNDLE_JOBS=2 \
  BUNDLE_PATH=/bundle

ADD . $APP_HOME
```

### docker-compose.yml

```
db:
  image: postgres
web:
  build: .
  command: bundle exec rails s -p 3000 -b '0.0.0.0'
  volumes:
    - .:/myapp
  ports:
    - "3000:3000"
  links:
    - db
  volumes_from:
    - bundle
  environment:
    - DB_USERNAME=postgres
    - DB_PASSWORD=
    - DB_HOST=db
bundle:
  image: myapp_web
  volumes:
    - /bundle
```

使っている感じ、環境変数は [dotenv](https://github.com/bkeepers/dotenv) 等で設定するより docker-compose.yml で設定するほうが無難そうです。

### bundle install で取得した gem をキャッシュする

https://docs.docker.com/compose/rails/ の場合、 `bundle install` や `bundle update` のたびに全 gem を取得するので時間がかかります。
上記の `docker-compose.yml` にあるように bundle path を別の volume に切り出すことでキャッシュできます。

```
docker-compose run web bundle install
docker-compose run web bundle update
```

## Rails new

上記の `Dockerfile` と `docker-compose.yml` を使って Rails プロジェクトを作成します。
(https://docs.docker.com/compose/rails/ と同じです)

まず、次のファイルを同じディレクトリに置きます。

- Gemfile
- Dockerfile
- docker-compose.yml

`Gemfile` の内容:

```ruby
source 'https://rubygems.org'
ruby '2.2.3'

gem 'rails'
```

次のコマンドを実行します。

```sh
docker-compose build
docker-compose run web bundle install
docker-compose run web bundle exec rails new . --force --database=postgresql --skip-bundle
```

これでホストOSに ruby さえ入れずに `rails new` できました。

生成されたファイルが root ユーザーの所有になってしまうので `chown` で自分の所有に変えます。
(これ改善方法あれば教えてください。)

`database.yml` で `host: db` を指定します:

```yml
default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>
  host: <%= ENV['DB_HOST'] %>

development:
  <<: *default
  database: myapp_development

test:
  <<: *default
  database: myapp_test
```

これで DB も使えるようになるので次のコマンドを実行します。

```sh
docker-compose run web bundle exec rake db:setup
```

Rails server を起動:

```sh
docker-compose up
```

`docker-compose run web bundle exec rails s` でも起動できますが、ホストOSのポートとDocker中のポートを結びつける必要があるので
`docker-compose up` を使います。

テスト実行:

```sh
docker-compose run web bundle exec rake spec
docker-compose run web bundle exec rake test
```

## [小ネタ] docker-compose up 時の rails server のログ

Linuxで `docker-compose up` したとき以前起動した時の `rails server` のログが無駄に表示されるので邪魔なときは
次のコマンドで消してからサーバーを起動します。
(この現象はなぜか Mac では発生しません)

```sh
docker-compose rm web
docker-compose up
```

## 雑感

こんな感じでちょっとの設定ファイルを用意すれば rails を動かす環境を作れます。
ホストOSに余計なものが入らないのは快適ですね。
プロジェクトで他の人が docker-compose を使いたくない場合も database.yml だけリポジトリ内で工夫すれば使えます。

## 若干困っていること

- `rails generate` 等のコマンドを docker-compose 経由で実行した時に生成されるファイルが root 権限になるのでユーザー権限で作りたい(Linuxでのみ発生)
- ~~`rails server` の古いログを `docker-compose up` 時に表示しないようにしたい~~
    - 以前立ち上げた docker が残っているので、 `docker-compose stop` すると消せます 
- ~~`rails console` で日本語が使えない~~ コメント欄で解決方法を教えて頂きました。ありがとうございます!!
- ~~`rails server` 起動中に `byebug` が使えない (`byebug -R` をうまく使えればいけそう)~~
    - `docker-compose up` ではなく `docker-compose run --service-ports web bundle exec rails s -b 0.0.0.0` でサーバーを起動すればデバッガが使えます。

解決方法ご存じの方がいれば教えてください :pray: