Docker on Mac な Rails 環境を10倍速くする
こんにちは! @sukechannnn です。
「Docker を Mac で使うと遅い」とは聞いてたのですが、本当にめちゃくちゃ遅い開発環境に遭遇してしまいました...はい、弊社です。僕が入社した時、Rails のプロセスを立ち上げるのにとても時間がかかる状態で、Rails console を立ち上げる度に「マダカナ-」となってました。
最初はそれで開発していたのですが、しばらくして「これは遅い...なんとかしたい...!」という気持ちが高まってしまいました。そして試行錯誤の結果、最終的に Rails console を立ち上げるのにかかる時間が 50秒→5秒 に、データ初期化も 10分→1分 になり、高速化することができました!
Docker の高速化については記事がたくさんあって、調べるのがとても大変でした。今回速くなったのも有効な環境とそうでない環境があると思うので、速くなった理由とともに解説してみます。
まずは結論をば...
volume を定義してそこに gem をインストールする
Docker の volume とは、ホストマシン内に作成できる Docker 専用のデータ保存領域です。
※ docker docs より
gem を置くための volume を定義して、そこに gem をインストールするようにしました。こうすることで、ライブラリのファイル読み込みが高速化し、Rails が素早く立ち上がるようになります。
具体的には以下の通りです。今回の高速化に寄与する部分のみ書いてます。
Dockerfile
FROM ruby:3.0.2-alpine3.13
...省略
RUN mkdir /app
WORKDIR /app
ENTRYPOINT [ "./entrypoint.sh" ]
docker-compose.yml
version: '3'
services:
backend:
build: .
container_name: "backend"
hostname: backend
ports:
- '3001:80'
command: "bundle exec rails s -p 80 -b 0.0.0.0"
volumes:
- .:/app # Rails のルートディレクトリを bind mount してる
- bundle:/usr/local/bundle # gem インストール用の volume を定義する
entrypoint.sh
#! /bin/sh -eu
gem install bundler
# docker-compose.yml で定義した volume に gem をインストールするよう bundler に設定
bundle config set --local path /usr/local/bundle
bundle install -j4
exec "$@"
これで、volume を使わずに(./vendor/bundle とかに直接)gem をインストールした場合に比べて10倍速くなります。
速くなる理由
ホストマシンと Docker でファイルをやり取りする方法には bind mount と volume の2種類があります。そのうち volume は、Docker 内からしか参照されず、ファイル読み書き時にホストマシンとの同期処理を行いません。そのため、bind mount に比べてめっちゃ速いのです。
開発を進めていって次第にインストールされた gem が増えてくると、その分だけ多くのファイルを Docker 上の Rails が読み込むことになります。この時、bind mount だとだんだん遅くなっていってしまいます。volume を使うことでそれを防げるので、bind mount で gem を読み込んでる場合は変更してみると速くなると思います。
まとめ
たくさんのファイルを読み込む、かつアプリケーションから変更されない(同期が必要ない)場合には、volume を使おう(node_modules とかでも使えるよ)
FYI: docker-compose.yml における bind mount と volume の書き方(short syntax)
docker-compose.yml に bind mount / volume を定義する short syntax の書き方がややこしいかったのでメモ。調べ方が悪いのかなかなか出てこなくて、やっと分かったよ...。
(公式ドキュメントに丁寧な説明があった → https://docs.docker.com/compose/compose-file/compose-file-v3/#volumes)
short syntax
volumes: に
- コロンの左にホストマシンのパスを指定すると bind mount になる
- そうでない場合は volume になり、コロンの左(下で言う
bundle
)はキー名になる
volumes:
- .:/app # Rails のルートディレクトリをdockerの /app に bind mount する
- bundle:/usr/local/bundle # gem インストール用の volume を定義する
long syntax
こちらは type を書くので分かりやすいですね。
version: "3.9"
services:
web:
image: nginx:alpine
volumes:
- type: volume # volume
source: mydata
target: /data
volume:
nocopy: true
- type: bind # bind mount
source: ./static
target: /opt/app/static
株式会社アルダグラムのTech Blogです。 世界中のノンデスクワーク業界における現場の生産性アップを実現する現場DXサービス「KANNA」を開発しています。 採用情報はこちら: herp.careers/v1/aldagram0508/
Discussion