前回の記事では、Dockerのイメージやコンテナといった基礎についてまとめました。

今回はより実践的な内容として、RubyとPostgreSQLでRuby on Railsアプリケーションの開発環境をつくる方法について書きます。 また、あわせてBundlerでインストールしたGemをキャッシュする方法についてもまとめます。

この記事の内容により、Dockerさえインストールすればすぐに開発環境ができあがるようになります。 ぜひ試してみてください。

動作環境

この記事の内容は、次の各バージョンで動作を確認しています。

  • macOS 10.12.2
  • Docker 1.12.3
  • Ruby on Rails 5.0.0.1
  • PostgreSQL 9.6.1

コンテナ

Railsアプリケーションを動かすには、最低限Webサーバ(5系だとPumaなど)とデータベースサーバを動かす2つのコンテナが必要になります。 また、Dockerの性質上、この2つだけだとGemfileが更新される度にすべてのGemをインストールすることになるため、Gemのキャッシュ用のコンテナも用意します。

この3つのコンテナの役割について、以下に簡単にまとめます。

1. webコンテナ

RubyやPostgreSQLクライアントなど、Railsアプリケーションの動作に必要なライブラリをインストールします。 また、ホストマシンから参照できるようにポートも開放します。

このコンテナにはRubyの公式イメージを利用すると便利です。

2. dbコンテナ

PostgreSQLの動作に必要なライブラリのインストールや設定を行ないます。 こちらもPostgreSQLの公式イメージを利用すればよいでしょう。 このDockerfileはGitHubから参照できます。

3. bundleコンテナ

Dockerfileは命令を行単位でキャッシュしますが、1行でも変更があると以降すべての命令が実行されます。 このため、Dockerfileでbundle installすると、Gemを追加する度にすべてのGemをインストールし直すことになってしまいます。

これを回避するために、Gemをインストールするためのコンテナを別途用意します。

構築手順

以下に、RubyとPostgreSQLでRailsアプリケーションの開発環境をつくる手順について説明します。

なお、ここではホストマシンにRubyをインストールする必要のないよう、webコンテナをとおしてRailsプロジェクトを生成しています。 開発中のプロジェクトがすでにある場合は、当該の箇所を読み飛ばしてください。

1. Dockerfileを書く

まず、webコンテナをつくるためのイメージを作成します。 Docker HubにRubyの公式イメージがあるので、これをベースに処理を書いていきます。

Dockerfile:

FROM ruby:2.3.3
RUN apt-get update -qq && \
    apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /app
WORKDIR /app
ADD Gemfile .
ADD Gemfile.lock .
ENV BUNDLE_JOBS=4 \
    BUNDLE_PATH=/bundle
RUN bundle install
ADD . .

各命令の詳細は、公式ドキュメントをご覧ください。

上記でbundle installしていますが、これはまずはじめにGemfileをとおしてrailsをインストールするために書いています。 このGemをとおしてrails newでプロジェクトを生成します。 これにより、ホストマシンにRubyをインストールする必要がなくなります。

既存のプロジェクトがある場合はこの行は省略して構いません。

2. Docker Composeの設定を書く

次に、Docker Composeの設定例を以下に示します。

version: '2'
services:
  web:
    build: .
    command: bundle exec rails s -p 3000 -b 0.0.0.0
    container_name: web
    depends_on:
      - db
    ports:
      - 3000:3000
    stdin_open: true
    tty: true
    volumes:
      - .:/app
    volumes_from:
      - bundle
  db:
    image: postgres
  bundle:
    image: busybox
    volumes:
      - /bundle

各オプションの詳細は公式ドキュメントにありますが、簡単に説明しておきます。

webコンテナのdepends_onは、このコンテナの前に起動するコンテナを指定します。 また、volumes_fromで他のコンテナのボリュームをマウントします。 ここではGemの保存先として/bundleをマウントしています。

stdin_openttyは必須ではありませんが、pryでデバッグする際は必要になりす。

db/bundleコンテナは設定がほとんどありませんが、これはDocker Hubのイメージを利用しているため設定が少なく済んでいます。 特にbundleコンテナはただのボリュームなので、busyboxという最小構成のコンテナを生成するイメージを利用しています。

3. Gemfile

webコンテナをとおしてrails newするために、あらかじめGemfileGemfile.lockをつくっておく必要があります。 前述のとおり、既存のプロジェクトがある場合は次に進んでください。

Gemfile:

source 'https://rubygems.org'

gem 'rails', '5.0.0.1'
$ touch Gemfile.lock

4. Dockerfileをビルドする

次に、先ほど作成したDockerfileをDocker Composeをとおしてビルドします。 これがwebコンテナ用のイメージとなります。

$ docker-compose build

5. Railsプロジェクトを生成する

webコンテナを立てる準備ができましたので、これをとおしてRailsプロジェクトを生成します。 こうすることで、ホストマシンにRubyをインストールすることなくRailsアプリケーションの開発を始められます。

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

6. データベースの接続設定をする

開発環境(あるいは必要であればテスト環境も)データベースの参照先がコンテナになるので、database.ymlも修正する必要があります。 以下を参考に設定を行ないます。

config/database.yml:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>

development: &development
  <<: *default
  database: app_development
  username: postgres
  host: db

test:
  <<: *development
  database: app_test

hostにはdbコンテナ名を書きます。 postgresイメージは、デフォルトでユーザ名がpostgres、パスワードは空となっています。 変更した場合はこれも設定します。

7. Gemをインストールする

Railsプロジェクトを生成した際に--skip-bundleしたので、ここでGemをインストールします。

次のようにwebコンテナをとおしてインストールすることで、Dockerfileで設定したとおり/bundleディレクトリにインストールすることができます。

$ docker-compose run web bundle install

8. データベースをつくる

一般的なRailsアプリケーション開発のフローと同じく、まずはじめにデータベースをつくる必要があります。 これもwebコンテナをとおして行ないます。

$ docker-compose run web rake db:create

9. 各コンテナを起動する

これですべての準備が整いました。 次のコマンドでweb/db/bundleコンテナが立ち上がり、http://localhost:3000にアクセスすることで初期画面が表示されます。

$ docker-compose up

実用例

サーバを立ち上げる

先ほど「docker-compose upで立ち上げる」と書きましたが、これにより表示されるターミナル上の出力は各コンテナのログで、読み込み専用となります。 このため、binding.pryを仕込んでもターミナル上で実行することはできません。

これを解決するために、次のように各コンテナをバックグラウンドで動かしつつwebコンテナにだけアタッチします。 こうすることで、通常のrails sのようなふるまいをし、pryによるデバッグも可能となります。

$ docker-compose up -d && docker attach web

なお、ここでいうwebというのはdocker-compose.ymlcontainer_nameで指定した値です。

コンソールを立ち上げる

rails cによるコンソールの立ち上げも、同じようにwebコンテナをとおして行ないます。

$ docker-compose run web bundle exec rails c

サーバを停止する

docker-compose upで起動したコンテナをCtrl+Cで終了しても、各コマンドは正常に終了しません。 この弊害としてserver.pidが残り、次回以降rails sするときにエラーが出てしまいます。

各コンテナを正しく終了するには、次のコマンドを実行する必要があります。

$ docker-compose stop

参考記事

おわりに

Dockerを利用すると、ホストマシンにはDocker以外なにもインストールすることなく開発環境を構築することができます。 Railsプロジェクトにこれを準備しておくと、新しいメンバーもすぐに開発に参加できるため、開発がよりスムーズになると思います。

次回以降で、RedisやElasticsearchといったサービスのコンテナを導入する方法について書きたいと思います。