an odd fellow

仕事のメモ

「Ruby on Rails 5アプリケーションプログラミング」の環境をdocker-composeで作る。

背景

仕事でrailsを使うことになったがきちんと触ったことが無かったのでGWに試してみることにした。

railsの入門書はこれが良いらしいので買った。書籍ではローカルに環境を構築しているが、dockerを使ったほうが良いだろう。

Ruby on Rails 5アプリケーションプログラミング

Ruby on Rails 5アプリケーションプログラミング

docker-composeを用いたrailsの環境構築は公式にドキュメントが用意されているが、rubyrailsのバージョンが本で解説されている動作環境とは異なるので、合わせた環境をdocker-composeで構築してみる。また、本だとDBにSQLiteを使っているが現実的にはMySQLのほうが良いのでここも変えたい。

クイックスタート・ガイド:Docker Compose と Rails — Docker-docs-ja 17.06.Beta ドキュメント

動作環境

本の動作環境はこのようになっている。

Docker HubのrubyのイメージはCentOSが無いので妥協する。DBはMySQLを使うことにする。

https://hub.docker.com/_/ruby/

Dockerfile

上にも貼ったdocker-composeの公式のドキュメントを見ながら本の動作環境にバージョンを揃える。

FROM ruby:2.4.0
RUN apt-get update -qq && apt-get install -y build-essential mysql-client nodejs
RUN mkdir /myapp
WORKDIR /myapp
ADD Gemfile /myapp/Gemfile
ADD Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
ADD . /myapp

差分

  • rubyのバージョンを2.2.0から2.4.0へ変更。
  • apt-getでインストールするパッケージをlibpq-devからmysql-clientに変更。

メモ

  • apt-get update -qq-qq はエラー以外表示しないオプション。
    • ダウンロードやインストールのログは大量に出るから、もしコケたときのデバッグがしにくくなるから表示しないようにしてるのかな。

Gemfile

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

空のGemfile.lockもドキュメントと同様に用意する。

$ touch Gemfile.lock

差分

  • railsのバージョンを4.2.0から5.0.1へ変更。

docker-compose.yml

version: '2'
services:
  db:
    image: mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

差分

  • dbのimageをpostgresからmysqlに変更。
  • MYSQL_ALLOW_EMPTY_PASSWORDを追加。

メモ

  • Dockerfileで Add . /myapp しているから、docker-compose.ymlで改めて volumes: .:/myapp としなくてもいいんじゃ?と思ったが、そんなことなかった。
    • 前者は bundle install するためだけのファイルコピーで、後者はローカルのファイルの変更をコンテナに渡すために必要だった。
  • depends_onディレクティブは「dbを先に立ち上げよ。」という意味になるようだ。ただし、コンテナの立ち上がる順序は指定できても、立ち上がりシーケンスが終了したかどうかは確認してくれないようだ。これによって起こる問題は後述する。
  • MYSQL_ALLOW_EMPTY_PASSWORDは設定しないとMySQLが立ち上がってくれない。
    • 最初はこれを環境変数に設定せずにいたら立ち上がらなかった。docker logs でログを確認したところこれを設定しろというメッセージが出ていた。

rails new

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

差分

  • --databaseオプションで指定するデータベースをpostgresqlからmysqlへ変更。

メモ

  • --force は上書き許可オプション。
  • --skip-bundleは bundle installを行わないオプション。
    • bundle installはDockerfileでやるからここではスキップするのかな。

docker-compose build

ここはドキュメントと変わらない。

rails new したことによってrailsのファイルがローカルに振ってきて、Gemfileも更新されている。これを編集してJavascriptのランタイムを入るようにしてからdocker-compose buildする。

ここわかりづらかった。最初の docker-compose run web rails new . --force --database=postgresql --skip-bundle ではrailsしかインストールされてないミニマルなコンテナがビルドされる。そしてこれはrails newするためだけのコンテナになる。二回目の dockee-compose build でさっきrails newして降ってきたファイルも取り込んだコンテナが出来上がる。

差分

  • なし

データベース接続

ドキュメント通り、docker-compose uprails serverが立ち上がる。ブラウザで http://localhost:3000 を確認すると、上手く行っていれば、 Unknown database 'myapp_development' というエラーが表示される。また、ブラウザで直ぐに確認した場合は Unknown database servber 'db' というエラーも見える。これは前述した立ち上げ処理の終了まで面倒を見てくれないため、MySQLの起動よりも先にrails serverが立ち上がってしまうことが原因。こっちが出た場合はしばらく待つと前者のエラーに切り替わるはず。

差分

  • なし

データベース作成

ドキュメントどおり、 docker-compose run web rails db:create とすることでデータベースが作成される。改めて docker-compose up して http::/localhost:3000 にアクセスすると繋がるようになる。

f:id:roronya:20180430190754p:plain

差分

  • なし

メモ

なんでdocker-compose runの結果が、改めてdocker-compose upしても保持されたままなの?

まず、docker-compose upしてCtrl-Cで止めた後、再度docker-compose upするとコンテナを作り直す。よって、docker-compose runの結果が保持されることは無いはず。しかし、コンテナにマウントされたボリュームは作り直さないようだ。

マウントしているボリュームは、そのまま保持します

引用元: up — Docker-docs-ja 17.06.Beta ドキュメント

ということなので、MySQLのイメージはどこかにボリュームを持っているらしいことが推測できる。それで調べるとやはり持っているようだ。

実はデフォルトで永続化されるように設定されています。

引用元: Dockerの公式MySQLイメージの使い方を徹底的に解説するよ · DQNEO起業日記

というわけなので docker-compose run web rails db:create でデータベースが作成されるが、その変更はホスト側のボリュームに書き込まれ、改めて docker-compose up でコンテナを作り直してもデータベースは作成されたままというわけだった。

データベースを永続化する。

この手順はドキュメントに無い。

以上までやっても docker-compose down して再度 docker-compose up すると、 Unknown database 'myapp_development' というエラーが出る。上述の通り、デフォルトで永続化されてはいるので、そのパスをボリュームにすれば解消される。しかし、ホスト側からは見えづらいパスにボリュームがある(docker inspectするとパスがわかる)ので、明示的にマウントするボリュームは指定しておいた方が使い勝手が良い。

version: '2'
services:
  db:
    image: mysql
    environment:
      MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
    volumes:
      - ./tmp/db:/var/lib/mysql
  web:
    build: .
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

差分

  • ボリュームを./tmp/dbに指定した。

これで開発環境は整った。