August 13, 2017

Railsプロジェクトの開発環境をDockerで

主な目標:

  • ローカル環境にRubyは入れなくてもいい
  • 同じgemを何度もダウンロードしない

成果はGitHub

私自身はRails開発の経験がほとんど無いので、実用性は未検証。

出来上がりイメージ

~/myapp/
  Dockerfile
  Gemfile
  Gemfile.lock
  docker-compose.yml

myapp/が、あなたの新しいプロジェクトのディレクトリ。ここにRailsアプリのソースを置き、Dockerコンテナと共有する。

Dockerfile

まずは冒頭部:

FROM ruby:2.4.1
ENV LANG C.UTF-8
RUN apt-get update -qq \
 && apt-get install -y \
      build-essential \
      libpq-dev \
      libxml2-dev \
      libxslt1-dev \
      libqt4-webkit \
      libqt4-dev \
      xvfb \
      nodejs \
      npm

LANGの設定は、Railsコンソールを日本語化するために必要らしい。apt-getで入れるパッケージは、いい加減にチョイス。

続いて、開発するアプリの置き場:

ENV APP_HOME /rapp
RUN mkdir -p $APP_HOME
WORKDIR $APP_HOME

別に何でもいいけど、/rappとした。プロジェクト固有の名前にする必要は無い(プロジェクトごとにDockerイメージを作るわけなので)。

次は、Gem環境を設定して、Bundlerをインストール:

ENV GEM_HOME=/gems
ENV GEM_PATH=$GEM_HOME \
    BUNDLE_HOME=$GEM_HOME \
    BUNDLE_PATH=$GEM_HOME \
    BUNDLE_GEMFILE=$APP_HOME/Gemfile \
    PATH=$GEM_HOME/bin:$PATH
RUN gem install bundler

環境変数が多いが、要は、Gem関係をすべて/gemsに集約するのが目的。このディレクトリをDockerボリュームにマップすれば、bundle installするたびに同じgemを繰り返しダウンロードせずに済む。

仕上げに、Bundlerを使ってRailsをインストールする:

ADD Gemfile* $APP_HOME/
RUN bundle install

ローカルからADDするファイルは2つ。

  • ./Gemfile
  • ./Gemfile.lock

まずはRailsだけを入れるので、Gemfileはシンプル:

source 'https://rubygems.org'
gem 'rails', '4.2.9'

Gemfile.lockは空っぽにしとけばいい。

以上は、新規プロジェクト作成だけを想定したDockerfileだ。そのうち、開発が進んだプロジェクトをDockerイメージに固めて、テスト環境へ持って行きたくなるかもしれない。

その場合は、最後のADDを以下に置き換えておく:

ADD . $APP_HOME

これにより、アプリソースがDockerイメージに入る。

docker-compose.yml

使うDockerコンテナは2つ:

  • db ...データベース(Postgresql)用
  • web ...Webサーバ(アプリ)用

また開発時は、Dockerボリュームを2つ使う:

  • pgdata ...データベース用(dbコンテナの/var/lib/postgres/dataにマップ)
  • gems ...Gem用(webコンテナの/gemsにマップ)

さらに、ローカルのカレントディレクトリをwebコンテナの/rappへマップする。

以下が、開発時用のdocker-compose.ymlだ。

version: '3'

services:
    db:
        image: postgres:9.6.3
        environment:
            POSTGRES_DB: rappdb
            POSTGRES_USER: rails
            POSTGRES_PASS: pass88((
        volumes:
            - pgdata:/var/lib/postgresql/data

    web:
        build: .
        command: bundle exec rails s -p 3000 -b '0.0.0.0'
        environment:
            DB_HOST: db
            DB_NAME: rappdb
            DB_USERNAME: rails
            DB_PASSWORD: pass88((
        ports:
            - "3000:3000"
        volumes:
            - gems:/gems
            - .:/rapp
        depends_on:
            - db
volumes:
    pgdata:
        external: true
    gems:
        external: true

データベース名をrappdbとしている。もしDockerボリュームpgdataを、複数のプロジェクトで共有する場合は、データベース名をプロジェクト固有の名前に変えるべきだろう。

Dockerボリューム

Dockerボリュームを作るには:

$ docker volume create pgdata
$ docker volume create gems

新規Railsアプリ作成

前述の通り、最初のGemfileはシンプルだ:

source 'https://rubygems.org'
gem 'rails', '4.2.9'

Dockerイメージをビルドすると、bundle installによりRailsがインストールされる:

$ cd ~/myapp
$ docker-compose build

続いて、Railsアプリの雛形を自動生成する:

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

ここでは、以下の処理がおこなわれる。

  • 2つのDockerコンテナを実行
    • dbコンテナは不要だけど、depends_onしてるので…
  • webコンテナ上でrails newが:
    • カレントディレクトリ上にRailsアプリの雛形を生成
    • Gemfileを更新
    • bundle installを実施
    • Gemfile.lockを更新

–rmオプションによりDockerコンテナは破棄されるが、rails newが生成したRailsアプリのソースはローカルディレクトリに残る。必要なgemもダウンロードされる。

アプリのコンフィグ

データベース用のコンフィグ(config/database.yml)を編集する:

default: &default
  adapter: postgresql
  encoding: unicode
  pool: 5
  host: <%= ENV['DB_HOST'] %>
  username: <%= ENV['DB_USERNAME'] %>
  password: <%= ENV['DB_PASSWORD'] %>
development:
  <<: *default
  database: <%= ENV['DB_NAME'] %>_development
test:
  <<: *default
  database: <%= ENV['DB_NAME'] %>_test
production:
  <<: *default
  database: <%= ENV['DB_NAME'] %>
  username: <%= ENV['RAPP_DATABASE_USERNAME'] %>
  password: <%= ENV['RAPP_DATABASE_PASSWORD'] %>

docker-compose.ymlで仕込んだ環境変数を使ってDB名やパスワードを設定している。production用のusername/passwordも、専用の環境変数で設定すれば良い。

開発

$ docker-compose up -d
$ docker-compose exec web rake db:setup db:migrate

これで、http://localhost:3000/を開けば、お馴染みの画面が出るはず。

gemを追加したりバージョンを調整するなら:

$ docker-compose exec web bundle install
$ docker-compose exec web bundle update

generatorを使うなら:

$ docker-compose exec web rails g controller hoge

後始末:

$ docker-compose down

rakerailsコマンドは、binstubs済み(他にもありそう)。binstubsされてないコマンドを使う場合は:

$ docker-compose exec web bundle exec <YOUR-COMMAND HERE>

トラブルシューティング

pg gemのバージョン

Railsアプリは大量のgemに依存しているので、互換性の問題が起きることも多い。例えば以下の組み合わせでは、うまく動かなかった。

  • Ruby 2.4.1
  • Rails 4.2.9
  • pg gem 0.21.0

ググった結果、Rilasを5.xに上げるか、pg gemを下げるのが回避策だと分かった。後者なら:

  • rails newするときに–skip-bundleを付けて、gemダウンロードをスキップ
  • rails newが生成するGemfileを編集して、pgのバージョンを0.20.0へ下げる
  • あらためてbundle installを実行

thor gemにも相性問題

thor 0.20.0とも相性が悪いらしい。Dockerイメージのbuild時にこれが入ると、その後のrails newが失敗した。

  • 最初に使うGemfileに、gem 'thor', '0.19.4'を追記
  • もし0.20.0が入ってしまったら、アンインストール(bundle uninstall thor -v 0.20.0)

いろいろ設計ミス

  • gemsボリュームは、プロジェクトごとに用意する必要があるのかな?
    • だとすると、gemsという名前はマズイなぁ
  • コンテナ内では、アプリのディレクトリ名が/rappなので、メインモジュール名がRappになってしまう(config/application.rb)
    • このモジュール名って、手編集して大丈夫かな?
Tags: ruby docker rails