docker-compose で Rails の開発環境を作る

# docker-compose で Rails の開発環境を作る

docker-compose (fig) を利用して Rails の開発環境を作ってみます。個人的には Docker を本番環境で運用するのはまだちょっと怖いなーという感じなのですが、開発環境やテスト環境でならもう全然使えるレベルかなと思います。例として Rails 4.2 + PostgreSQL 9.4 の環境セットアップ。Docker の基本的な操作はひととおり抑えているものとします。

- [Docker, Rails, & Docker Compose together in your development workflow](http://blog.carbonfive.com/2015/03/17/docker-rails-docker-compose-together-in-your-development-workflow/)

こちらの記事を参考にしてます。

- [Sample rails project on docker](https://github.com/skyriser/rails_on_docker)

この記事で作ったプロジェクトはこっち。

## コンテナ戦略

原則 `1 Container 1 Service` に従います。従って超基本構成なら

- Rails コンテナ
- DB コンテナ

となります。後から Redis を追加したいとなった場合は、

- Rails コンテナ
- DB コンテナ
- Redis コンテナ

となります。1コンテナで複数のサービスを動作させるのは基本的にはやらない。

## Rails の Dockerfile を準備する

Ruby が初めから入ってるイメージを選ぶと無駄がないです。以前は CentOS 等のイメージを持ってきてあれこれセットアップして…みたいなのでしたが今は色んなオフィシャルコンテナが用意されてるのでとても簡単ですね。こんな感じです。

```
FROM ruby:2.2.2
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev postgresql-client

RUN mkdir /app

WORKDIR /tmp
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install

ADD . /app
WORKDIR /app
```

`/tmp` に Gemfile をコピーしてるのは bundle install を高速化するというやつですね。Docker 1.1 から ADD キャッシュが入ったので、Gemfile が変更されなければ再ビルドをしても、高速にセットアップすることが出来ます。Dockerfile はリポジトリのルートにおいておくのが良いかと。

## docker-compose でオーケストレーション

`docker-compose.yml` を同じくリポジトリのルートに準備。復数のコンテナを同時に利用できるよう設定を行います。

```yaml
db:
  image: postgres
  expose:
    - "5432"
web:
  build: .
  command: bundle exec rails s -p 3000 -b '0.0.0.0'
  volumes:
    - .:/app
  ports:
    - "3000:3000"
  links:
    - db
```

DB コンテナの Dockerfile は用意しませんでしたが、ここではオフィシャルの postgres イメージをそのまんま使ってます。ポート版は 5432 なので、これはそのまま `expose` (コンテナ外へポート開放) してます。

Rails コンテナの Dockerfile は先ほど準備したものを `build: .` で指定、`links` によって DB コンテナを同時に起動するようにしています。Rails コンテナは Docker 外から接続させたいので `expose` ではなく `posts` を利用してます。expose と ports はこんな感じ。

- expose: Docker 内の他のコンテナ向けにポートを開放
- ports: Docker 外にポートを開放、Docker 内の他のコンテナからもアクセス可能
 - `ports` は `Dockerホストポート:Dockerコンテナポート` で対応してます

オーケストレーションはこれだけでOKですが、DB は別コンテナになるので Rails の `database.yml` を書き換えておきます。

```
development: &default
  username: postgres
  host: db
```

`db` となってるのはコンテナ名ですね。

## コンテナ起動

後は `docker-compose` コマンドを利用してコンテナを起動するだけです。

```
docker-compose build
```

まずコンテナをビルドします。その後、DB の作成処理などが必要になるので、

```
docker-compose run web bin/rake db:create db:migrate db:seed
```

などとして Migration を掛けます。 web を対象にコマンドを実行していますが、DB コンテナにリンクしているため同時にDBコンテナも起動します。終わったら

```
docker-compose up
```

基本的にはこれだけで2つのコンテナが上がってくると思います。Rails コンテナのコマンドに `rails server` を指定してるので `docker-compose up` するとサーバーのログがそのまま出ると思います。この状態で localhost:3000 にアクセスすると Rails が起動していることが確認できるはず。(boot2docker 使ってる場合は `boot2docker ip` で出てくるIPに変える)

この状態ができれば、リポジトリのファイルが Rails コンテナとリンクしているため、ローカルのファイルを編集すれば、コンテナ上のアプリケーションに反映されます。つまりいつも通り開発ができるということですね。

## コンテナ起動後の操作

Migration を行いたいなどの場合、起動中のコンテナに対する操作は行わず、 `docker-compose run` を利用して別途コンテナを起動する形になります。

- 各種操作の対応表

|ローカル開発時|Docker Compose|
|----|----|
|rails g model Member|docker-compose run web rails g model Member|
|rake db:migrate|docker-compose run web rake db:migrate|
|rails dbconsole|docker-compose run web rails dbconsole|
|RAILS_ENV=test bundle exec rake db:setup|docker-compose run -e RAILS_ENV=test web bundle exec rake db:setup|

従来のローカル開発に比べてちょっとテンポ悪くなるところもあるんですが、依存するミドルウェアが多い等のコンテナが沢山あるような環境であるほど恩恵を受けられると思いますよ。

### 他の開発メンバーに環境を配る

`Dockerfile` と `docker-compose.yml` をリポジトリに含めておけば、リポジトリを clone してもらって、docker 動くようにしてから

```
$ docker-compose build
$ docker-compose run web bundle exec rake db:setup
$ docker-compose up
```

を叩いてもらうだけです。ruby や mysql をインストールみたいなのも必要ないですしね。ただ、メンバーが Docker 動く環境を持ってないとつらいですね…

## その他

雑感とかTipsとか。

### ローカル開発環境を完全に捨てることはまだ難しい

`rails new` する時とか、ちょっとこの gem 試したいなー、の時とか Docker 外の環境を利用することはゼロではないので、完全にコンテナベースとするのはまだ難しい印象です。やり方が悪いのかもしれませんが。

ただ、メイン開発メンバーでない限り例えば、デザイナーさんに環境を渡して開発してもらう、等の場合にはかなりの威力を発揮すると思います。ちょっとコマンド叩いてもらうだけでアプリケーション起動しますからね。

### インフラ構築にストレスが無い

`docker-compose.yml` にちょろっと掛けば Redis やら Memcached やらすぐに利用できるようになるのメッチャ快適です。しかもアプリケーション固有のバージョンを指定して利用できますからバグの再現性なども高く快適です。

### Gemfile の更新がちょっとストレス

仕方ないとはいえ、Gemfile を更新するとイメージを再ビルドすることになってしまいます。コンテナ再構築時には、1から `bundle install` することになるので結構な時間がかかります。こういうテクニックも考えられますが、まあそこまでやるかなあ…という気はしますね。

```
# Rails 等基本的な Gemfile をまとめて install することでちょっと時間を短縮
RUN gem install rails pg rspec guard factory_girl ....
# アプリケーション本体の bundle install
COPY Gemfile Gemfile
COPY Gemfile.lock Gemfile.lock
RUN bundle install
```

### `docker-compose` コマンド長い！

元は `fig` というツールだったので私の場合はエイリアスを割り当てててます。

```
alias fig='docker-compose'
```

```
fig build
fig up
fig run web rails console
```

とかですね。