Dockerを導入した新規Railsアプリの開発方法に関する記事を以前紹介しました。
今回は既存のRailsアプリをDocker化する方法について説明をします。
新規アプリとの違いですが、新規の場合はデータも特にないのでDBに関してもDockerイメージから作成し、docker-composeでアプリと接続をします。
一方、既存アプリの場合はローカルDBがすでに構築されておりデータもその中に存在しています。
今回はRailsのDokcer化ができればDBに関してはローカルのものを参照しても特に問題がありませんでした。
ですので、既存のRailsアプリについてRails部分をDokcer化し、DBはDocker上からローカルを参照するという環境を構築することをゴールに今回の説明をしていきたいと思います。
今回のゴール

なお、既存のアプリのサンプルは以下の記事の紹介したものを利用します。
目次
Dockerファイルの作成
まずはDockerファイルを作成します。
Dockerfile
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
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
COPY Gemfile $APP_HOME/Gemfile
COPY Gemfile.lock $APP_HOME/Gemfile.lock
RUN bundle install
docker-composeの作成
次にdocker-composeを作成します。
今回はRailsアプリだけをDocker上にのせるため、DBに関する情報はdocker-composeには書きません。
docker-compose.yml
version: "2"
services:
web:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/usr/src/app
ports:
- 3000:3000
command: /sbin/init
env_file: .env.dev
環境変数の作成
DBに接続するための情報を.env.dev
に書きます。
.env.dev
はdocker-composeで読み込まれ、Railsの環境変数にセットされます。
DBに接続するユーザーがrailsuser
、パスワードがrailspass
である場合、以下のようになります。
DB_USERNAME=railsuser
DB_PASSWORD=railspass
DB_HOST=docker.for.mac.localhost
ここがDokcerからローカル環境にアクセスするためのポイントになります。
Dokcerからローカルにアクセスする場合はホスト名をdocker.for.mac.localhostに指定します。
2022年2月追記: docker.for.mac.localhostは非推奨になりました
Desktop 4.3.0でdocker.for.mac.localhost
は非推奨および削除予定となっており、現在はhost.docker.internal
が推奨されています。1
databse.ymlの変更
先ほどの環境変数を読み込むように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') %>
socket: /tmp/mysql.sock
development:
<<: *default
database: hello_rails_development
test:
<<: *default
database: hello_rails_test
production:
<<: *default
database: hello_rails_production
username: hello_rails
password: <%= ENV['HELLO_RAILS_DATABASE_PASSWORD'] %>
結果確認
まずはbuildしてイメージを作成します。
$ docker-compose build
ビルドが完了したらdockerをupします。
$ docker-compose up
docker psでプロセスが立ち上がっていることを確認します。
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
7ebfcd9ead07 hellorails_web "/sbin/init" About an hour ago Up 5 seconds 0.0.0.0:3000->3000/tcp hellorails_web_1
先ほどのプロセス名を指定してdockerコンテナ内に入ります。
コンテナ内でRailsアプリを起動してあげればアプリにアクセスできるようになります。
$ docker exec -it hellorails_web_1 /bin/bash
[root@a1ba69d70f43:/usr/src/app]
bundle exec rails s -b 0.0.0.0
=> Booting Puma
=> Rails 5.1.5 application starting in development
=> Run `rails server -h` for more startup options
サンプルの例であれば、http://localhost:3000/homes にアクセスすれば既存のDBに存在するデータが表示されます。

より快適なDocker環境を構築するために
上記の方法で既存のRailsアプリをDocker化することができました。
しかし、開発効率をあげるために改善できる点があるので説明をしたいと思います。
改善の方針は以下の記事を参考にしています。
Dockerイメージのビルド回数を減らす
上記で作ったDockerの環境ではDockerファイルでbundle installを実行しているため、新しいgemを追加するためにbundle installを実行したい場合、毎回Dockerイメージを0から作成(ビルド)する必要があります。
Dockerイメージのビルドはゼロから改めてイメージを作成することになり時間がかかるので何度もビルドしていると開発効率が下がってしまいます。
そこでentrykitを導入し、bunble installはDockerのプロセスが起動する直前で実行されるように変更します。
Dockerfile
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", "--"]
上記のように変更することでbundle installの実行をDokcerイメージのビルド工程から、Dockerプロセスの起動時(docker-compose up)に変更することができました。
その結果、bundle installをするたびに無駄な処理(Dockerイメージのビルド)がされなくなりました。
Dockerイメージを作成する時にbundle installをしないので、GemfileとGemfile.lockのコピーコマンドもDockerfileから削除しても大丈夫です。
bundle installの実行時間を減らす
entrykitを導入することで、bundle installをDockerイメージのビルドを毎回しなくて済むようになりました。
これでDockerプロセス起動時に純粋にbundle installだけを実行できるようになり、開発効率が上がるようになりました。
しかし、Dockerコンテナを削除してしまうとGemfileの内容を改めてゼロからインストールしなおさないといけなくなってしまいます。
そこでdata volumeを活用して、bundle installでインストールされたgemの内容を永続化できるように修正してあげます。
docker-compose.yml
version: "2"
services:
datastore:
image: busybox
volumes:
- bundle_install:/usr/local/bundle
web:
build:
context: .
dockerfile: Dockerfile
volumes:
- .:/usr/src/app
ports:
- 3000:3000
volumes_from:
- datastore
command: /sbin/init
env_file: .env.dev
volumes:
bundle_install:
driver: local
これでDockerコンテナが破棄された場合でもdata volumeを参照することで今までインストールしたgemをすぐに利用することができるようになります。