docker-composeで始める快適なRails開発環境構築とDocker詳細解説

Ruby

アプリケーションをDocker化するにあたりdocker-composeという言葉を聞いたことがある方も多いと思います。

今回、docker-composeを利用してDockerでRailsアプリの開発環境の構築したので、docker-composeを利用したRailsアプリを作成する手順と、Dockerファイルやdocker-composeの解説もしたいと思います。

なお、Railsアプリで利用するDBはMySQLを想定しています。

作成したDockerファイルの解説

DockerファイルとはDockerコンテナの構成内容が記述されたファイルのことです。

Dockerファイルの内容をもとにDockerイメージの作成が行われ、Dockerコンテナというインスタンスを起動させることで利用できます。

今回作成した、Railsアプリを実行させるためのDockerファイルは以下のようになります。

FROM ruby:2.4.0
ENV LANG C.UTF-8
RUN apt-get update -qq && apt-get install -y build-essential libpq-dev nodejs less

# alias
RUN echo 'alias ll="ls -laG"' >> /root/.bashrc

ENV APP_HOME /usr/src/app

ENV ENTRYKIT_VERSION 0.4.0
RUN wget https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && tar -xvzf entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && rm entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz \
  && mv entrykit /bin/entrykit \
  && chmod +x /bin/entrykit \
  && entrykit --symlink


RUN mkdir $APP_HOME
WORKDIR $APP_HOME

ENTRYPOINT [ \
  "prehook", "ruby -v", "--", \
  "prehook", "bundle install -j3 --path /usr/local/bundle", "--"]

このDockerファイルではrubyイメージをベースに必要なパッケージのインストールと作業ディレクトリの作成を行なっています。

Dockerファイルの中盤あたりでentrykitというものをインストールしているのですが、entrykitとはコンテナ内のプロセスを起動する際に便利な軽量initシステムのことです。
今回の例でいうとENTRYPOINTの部分で活用されています。

ENTRYPOINTにはコンテナを起動するときに実行されるコマンドを記述します。
ここにprehookというものが記述されていますがこれが先ほどインストールしたentrykitの機能の一つになります。

prehookを利用することでメインプロセス起動前に複数のコマンドを実行することができます。
今回の例だとbundle installとrubyのバージョンを表示するのがそれにあたります。

DockerのドキュメントなどではRUN bundle installと記述することでgemのインストールを行っていますが、この場合だとbundle installしたい時に毎回Dockerのビルドをし直さないといけません。(ビルドとはDockerのイメージを再作成するときに行うものです。)

しかし、prehookでbundle installを実行するようにするといちいちビルドをする必要がなくなります。
さらに、prehookを利用することで「コンテナ起動前に行っておきたい処理を確実に実行することができる」、「複数の前処理をまとめて管理することができる」といったメリットを受けることができます。

さらにgemのインストール先のディレクトリの内容をキャッシュさせておくことでgemを毎回ゼロからインストールする必要がなくなるため、高速化させることができます。
キャッシュの実装はData volumeを利用しています(設定の仕方については後述します)。

作成したdocker-composeの解説

先ほど、実際にDockerを利用する際はDockerイメージからDockerコンテナというインスタンスを作成するという話をしました。
docker-composeはそのDockerコンテナを複数管理、起動したい場合に利用するためのものです。

Railsアプリでは、Rails自身が動作するwebコンテナの他にMySQLが動作するdbコンテナやデータのキャッシュをしておくコンテナが必要になります。
こういった複数のコンテナが必要で、さらにお互いが依存関係を持っている時などにdocker-composeが利用されます。

今回は以下のようなdocker-composeを作成しました。

version: "2"
services:
  datastore:
    image: busybox
    volumes:
      - bundle_install:/usr/local/bundle
      - db:/var/lib/mysql
  db:
    image: mysql:5.7
    environment:
      - MYSQL_USERNAME=root
      - MYSQL_ROOT_PASSWORD=password
    ports:
      - 3306:3306
    volumes_from:
      - datastore
  web:
    build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - .:/usr/src/app
    ports:
      - 3000:3000
    volumes_from:
      - datastore
    command: /sbin/init
    env_file: .env.dev
    depends_on:
      - db

volumes:
  bundle_install:
    driver: local
  db:
    driver: local

今回のdocker-composeには3つのコンテナが定義されています。

  • web
    • Railsが起動するコンテナ
  • db
    • MySQLが起動するコンテナ
  • datastore
    • bundle installでインストールされたgemやdbのデータをキャッシュさせておくためのコンテナ

ベースとなるイメージですが、webは先ほど作成したDockerファイルからビルドされたもので、dbとdatastoreについてはすでに配布されているDockerイメージを利用しています。

datastoreで利用されているbusyboxというイメージは必要最低限の機能だけを持っている軽量のイメージです。
datastoreはデータのキャッシュだけできればいいのでbusyboxを利用しています。

環境変数などはenvironmentで定義することができます。
dbでいうところの「MYSQL_USERNAME」や「MYSQL_ROOT_PASSWORD」がそれにあたります。

webの場合はenv_fileを定義し、外部ファイルに環境変数などを定義しています。
ここにはDBの接続情報などを記述しておくのですが、例えば、外部API(Twitter APIなど)のアクセスキーなどをRailsアプリで利用する場合はこのファイルの中に一緒に定義しておくとよいでしょう。

docker-composeの最後のvolumes:の部分でDockerのどのデータをキャッシュさせるか定義をしています。
データをキャッシュさせる機能は「Data volume」とよばれています。
datastoreにwebとdbのvolumeをそれぞれを紐づけることでキャッシュを実現させています。

使い方

これから実際にRailsを起動させるための手順を説明します。

ファイルの配置

まずRailsアプリを作成するディレクトリに以下のファイルを配置します。今回準備するファイルは以下の5つです。

  • Dockerfile
    • 先ほどのもの
  • docker-compose.yml
    • 先ほどのもの
  • Gemfile.lock
    • 空でいい
  • Gemfile
    • 後述
  • .env.dev
    • 後述

Gemfileは以下のようにします。

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

.env.devは以下のようにします。

DB_USERNAME=root
DB_PASSWORD=password
DB_HOST=db

これで、以下のようになっていることを確認します。

$ ls -l
#-------------
total 24
-rw-r--r-- 1 nishina staff 720 4 23 15:10 Dockerfile
-rw-r--r-- 1 nishina staff 53 4 23 15:11 Gemfile
-rw-r--r-- 1 nishina staff 0 4 23 15:11 Gemfile.lock
-rw-r--r-- 1 nishina staff 618 4 23 15:11 docker-compose.yml
#-------------

雛形の作成

まずはイメージを作成します。

$ docker-compose build

ビルド後イメージがあるか確認します。

$ docker images
#-------------
REPOSITORY TAG IMAGE ID CREATED SIZE
railsdockerstarter_web latest dc0d99c763a5 19 seconds ago 713 MB
#-------------

Railsアプリの雛形を作成します。

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

docker volumeにbundleとdbが作成されていることを確認します。
作成されているということはデータがキャッシュされているということを意味します。

$ docker volume ls
#-------------
DRIVER VOLUME NAME
local railsdockerstarter_bundle_install
local railsdockerstarter_db
#-------------

プロセスを起動します。

$ docker-compose up

プロセス確認を確認します。dbのプロセスが立ち上がっていればOKです。

$ docker ps
#-------------
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
81cae414b3a3 mysql:5.7 "docker-entrypoint..." 6 minutes ago Up 6 minutes 0.0.0.0:3306->3306/tcp railsdockerstarter_db_1
#-------------

Railsアプリのデータベース設定

DBの接続情報が書かれているdatabase.ymlを変更します。
.env.devに記述されているデータベースの設定が利用できるように変更します。

$ vim config/database.yml

#--------
default: &default
  adapter: mysql2
  encoding: utf8
  pool: 5
  username: <%= ENV.fetch('DB_USERNAME', 'root') %>
  password: <%= ENV.fetch('DB_PASSWORD', 'dummy') %>
  host:     <%= ENV.fetch('DB_HOST', 'db') %>

development:
  <<: *default
  database: app_development

test:
  <<: *default
  database: app_test

production:
  <<: *default
  database: app_production

#--------

Railsアプリを実行

今回のdocker-composeではwebコンテナのコマンドは/sbin/initとなっているので、プロセスが立ち上がっただけではまだRailsアプリは起動されていません。

そこで、起動したwebコンテナにログインし、rails serverを実行します。(docker-compose.yml内のcommand:でrails serverを起動するとbyebugがうまく利用できなかったので今回はこのような手順となっています。)

webコンテナにログインします。(DBの中身などを確認する場合はdbコンテナの方にログインします。)

$ docker exec -it $WEB_CONTAINER_NAME /bin/bash
#例. docker exec -it railsdockerstarter_web_1 /bin/bash

コンテナ内でrails serverを実行します。

[root@defb01b99219:/usr/src/app]
$ bundle exec rails s -b 0.0.0.0

結果確認

これでhttp://localhost:3000/にアクセスをし、以下のように画面が表示されればOKです。

最後に

今回のファイルや手順についてはこちらのリポジトリに置いておきました。

参考