💨

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

Discussion