概要
原著者の許諾を得て翻訳・公開いたします。
- 英語記事: Ruby on Whales: Dockerizing Ruby and Rails development — Martian Chronicles, Evil Martians’ team blog
- 原文公開日: 2019/07/23
- 著者: Vladimir Dementyev
- サイト: Evil Martians -- ニューヨークやロシアを中心に拠点を構えるRuby on Rails開発会社です。良質のブログ記事を多数公開し、多くのgemのスポンサーでもあります。
更新情報
- 2019/09/06: 初版公開
- 2021/04/20: 原文更新を反映
訳注
Ruby on Whalesは、Ruby on Railsの他に、もしかすると「Boy on a Dolphin」にかけているのかもしれないと思いました。Whales(クジラ)はもちろんDockerのシンボルです。
まえがき
本記事は、私がRailsConf 2019で話した「Terraforming legacy Rails applications」↑の、いわばB面に相当します。この記事を読んで、皆さんがアプリケーション開発をDockerに乗り換えるとまでは考えていません(皆さんが以下の動画で若干言及しているのをご覧になっていたとしても)。本記事の狙いは、私が現在のRailsプロジェクトで用いている設定を皆さんと共有することです。それらのRailsプロジェクトは、Evil Martiansのproduction development環境で生まれたものです。どうぞご自由にお使いください。
クジラに乗ったRuby: Evil Martians流Docker+Ruby/Rails開発環境構築(翻訳)
原文免責事項: 英語版記事は最新の推奨事項に合わせて更新を繰り返しています。詳しくは記事末尾のChangelogをご覧ください(参考: 原文Changelog)。
私がdevelopment環境でDockerを使い始めたのは、かれこれ3年ほど前の話です(それまで使っていたVagrantは4GB RAMのノートパソコンではあまりに重たかったのでした)。もちろん、最初からバラ色のDocker人生だったわけではありません。自分のみならず、チームにとっても「十分にふさわしい」Docker設定を見つけるまでに2年という月日を費やしました。
私の設定を、(ほぼほぼ)すべての行に解説を付けてご覧に入れたいと思います。「Dockerをわかっている」前提のわかりにくいチュートリアルはもうたくさんですよね。
本記事のソースコードは、GitHubのevilmartians/terraforming-railsでご覧いただけます。
本記事の例では以下を用います。
- Ruby 2.6.3
- PostgreSQL 13
- NodeJS 11 & Yarn(Webpackerベースのアセットコンパイル用)
🔗 Evil Martians流Dockerfile
Railsアプリケーションの「環境」は、Dockerfile
で定義します。サーバーの実行、コンソール(rails c
)、テスト、rakeタスク、開発者としてコードとのインタラクティブなやりとりは、ここで行います。
ARG RUBY_VERSION
# 後述
FROM ruby:$RUBY_VERSION-slim-buster
ARG PG_MAJOR
ARG NODE_MAJOR
ARG BUNDLER_VERSION
ARG YARN_VERSION
# 共通の依存関係
RUN apt-get update -qq \
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
build-essential \
gnupg2 \
curl \
less \
git \
&& apt-get clean \
&& rm -rf /var/cache/apt/archives/* \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& truncate -s 0 /var/log/*log
# mimemagic gem用のMIMEタイプデータベースをダウンロード
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
shared-mime-info \
&& cp /usr/share/mime/packages/freedesktop.org.xml ./ \
&& apt-get remove -y --purge shared-mime-info \
&& apt-get clean \
&& rm -rf /var/cache/apt/archives/* \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& truncate -s 0 /var/log/*log \
&& mkdir -p /usr/share/mime/packages \
&& cp ./freedesktop.org.xml /usr/share/mime/packages/
# PostgreSQLをソースリストに追加
RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
&& echo 'deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main' $PG_MAJOR > /etc/apt/sources.list.d/pgdg.list
# NodeJSをソースリストに追加
RUN curl -sL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash -
# Yarnをソースリストに追加
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list
# アプリケーションの依存関係
# 外部のAptfileでやってる(後ほどお楽しみに!)
COPY Aptfile /tmp/Aptfile
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
libpq-dev \
postgresql-client-$PG_MAJOR \
nodejs \
yarn=$YARN_VERSION-1 \
$(grep -Ev '^\s*#' /tmp/Aptfile | xargs) && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
truncate -s 0 /var/log/*log
# bundlerの設定
ENV LANG=C.UTF-8 \
BUNDLE_JOBS=4 \
BUNDLE_RETRY=3
# bundler設定をプロジェクトのルートに保存する場合は以下をコメント解除
# ENV BUNDLE_APP_CONFIG=.bundle
# `bin/`や`bundle exec`を付けずにbinstabを実行したい場合は以下をコメント解除
# ENV PATH /app/bin:$PATH
# RubyGemsをアップグレードして必要なバージョンのbundlerをインストール
RUN gem update --system && \
gem install bundler:$BUNDLER_VERSION
# appコードを置くディレクトリを作成
RUN mkdir -p /app
WORKDIR /app
このDockerfileの設定は必要不可欠なもののみを含んでいますので、これを元にカスタマイズできます。このDockerfileで何が行われるかを解説します。
最初の2行に少々妙なことが書かれています。
ARG RUBY_VERSION
FROM ruby:$RUBY_VERSION-slim-buster
FROM ruby:2.6.3
みたいに適当な安定版Rubyのバージョンを書いておけばよさそうなものですよね。ここではDockerfileを一種のテンプレートとして用い、環境を外部から設定可能にしたいのです。
- ランタイム依存の厳密なバージョンは、
docker-compose.yml
の方で指定することにします(後述)。 apt
コマンドでインストール可能な依存のリストは、これも別ファイルに保存することにします(これも後述)。
また、PostgreSQLなどの他の依存関係に対応する正しいソースを追加するためにDebianのリリース(buster)を明示的に指定しています。
上に続く以下の4行は、PostgreSQL、NodeJS、Yarn、Bundlerのバージョンを定義します。
ARG PG_MAJOR
ARG NODE_MAJOR
ARG BUNDLER_VERSION
ARG YARN_VERSION
今どきDockerfileをDocker Composeなしで使う人などいないという前提なので、Dockerfileではデフォルト値を指定しないことにします。
続いて実際のイメージビルドが行われます。Dockerイメージのサイズを削減するためにslimベースのDockerイメージを使うので、最初にシステム共通の依存関係(GitやcURLなど)を手動でインストールします。
# 共通の依存関係
RUN apt-get update -qq \
&& DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
build-essential \
gnupg2 \
curl \
less \
git \
&& apt-get clean \
&& rm -rf /var/cache/apt/archives/* \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& truncate -s 0 /var/log/*log
システム依存のインストールの詳細については、アプリケーション固有のものについて説明するときに後述します。
次に、mimemagic gemで使うMIMEタイプデータベースをダウンロードするためだけにRUN
コマンドを実行します(mimemagic gemのライセンス問題はご存知ですか?)。
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
shared-mime-info \
&& cp /usr/share/mime/packages/freedesktop.org.xml ./ \
&& apt-get remove -y --purge shared-mime-info \
&& apt-get clean \
&& rm -rf /var/cache/apt/archives/* \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
&& truncate -s 0 /var/log/*log \
&& mkdir -p /usr/share/mime/packages \
&& cp ./freedesktop.org.xml /usr/share/mime/packages/
mimemagic gemを使わないのであれば、上のブロックは削除して構いません。
PostgreSQL、NodeJS、Yarnをapt
コマンドでインストールするために、それらのdebパッケージのリポジトリをソースリストに追加する必要があります。
- PostgreSQLの設定(公式ドキュメントに基づく)
RUN curl -sSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - \
&& echo 'deb http://apt.postgresql.org/pub/repos/apt/ buster-pgdg main' $PG_MAJOR > /etc/apt/sources.list.d/pgdg.list
原注: ここが、OSリリースをbusterにしたことを利用している箇所です。
- NodeJSの設定(NodeSourceリポジトリより)
RUN curl -sL https://deb.nodesource.com/setup_$NODE_MAJOR.x | bash -
- Yarnの設定(公式Webサイトより)
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& echo 'deb http://dl.yarnpkg.com/debian/ stable main' > /etc/apt/sources.list.d/yarn.list
今度は依存関係のインストールです(apt-get install
の実行など)。
COPY Aptfile /tmp/Aptfile
RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get -yq dist-upgrade && \
DEBIAN_FRONTEND=noninteractive apt-get install -yq --no-install-recommends \
libpq-dev \
postgresql-client-$PG_MAJOR \
nodejs \
yarn \
$(grep -Ev '^\s*#' /tmp/Aptfile | xargs) && \
apt-get clean && \
rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && \
truncate -s 0 /var/log/*log
まずAptfileという裏技について解説します。
COPY Aptfile /tmp/Aptfile
RUN apt-get install \
$(grep -Ev '^\s*#' /tmp/Aptfile | xargs) && \
Aptfileというアイデアはheroku-buildpack-aptから拝借しました。heroku-buildpack-aptは、Herokuに追加パッケージをインストールできます。このbuildpackを使っていれば、同じAptfileをローカルでもproduction環境でも再利用できます(buildpackのAptfileの方が多くの機能を提供していますが)。
私たちのデフォルトAptfileに含まれているのパッケージは、たったひとつです(私たちはRailsのcredentialの編集にVimを使っています)。
vim
私が携わっていた直前のプロジェクトでは、LaTeXやTexLiveを用いてPDFを生成しました。そのときのAptfileは、さしずめ以下のような感じにできたでしょう(当時私はこの技を使っていませんでしたが)。
vim
texlive
texlive-latex-recommended
texlive-fonts-recommended
texlive-lang-cyrillic
このようにすることで、タスク固有の依存関係を別ファイルに切り出し、Dockerfileの普遍性を高めています。
DEBIAN_FRONTEND=noninteractive
という行については、「answer on Ask Ubuntu」という記事をご覧になることをおすすめします。
--no-install-recommends
スイッチを指定すると、推奨パッケージのインストールを行わなくなるので容量を節約でき、ひいてはイメージをもっとスリムにできます。詳しくは「Xubuntu Geek: Save disk space with apt-get option "no-install-recommends" in Xubuntu」をご覧ください。
RUN
の最後の部分(apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* && truncate -s 0 /var/log/*log
)も目的は同じです。取得したパッケージファイルのローカルリポジトリ(ここまでで必要なものはすべてインストールできているので、これらはもはや不要です)や、インストール中に作成されたすべての一時ファイルやログをクリーンアップします。 特定のDockerレイヤにごみを残さないようにするため、このクリーンアップ作業は同じRUN
ステートメントの中で行う必要があります。
最後の部分は、もっぱらBundlerのためのものです。
# bundlerの設定
ENV LANG=C.UTF-8 \
BUNDLE_JOBS=4 \
BUNDLE_RETRY=3
# bundler設定をプロジェクトのルートに保存する場合は以下をコメント解除
# ENV BUNDLE_APP_CONFIG=.bundle
# `bin/`や`bundle exec`を付けずにbinstabを実行したい場合は以下をコメント解除
# ENV PATH /app/bin:$PATH
# RubyGemsをアップグレードして必要なバージョンのbundlerをインストール
RUN gem update --system && \
gem install bundler:$BUNDLER_VERSION
LANG=C.UTF-8
は、デフォルトロケールをUTF-8に設定します。これを行わないとRubyが文字列でUS-ASCIIを使ってしまうので、かわいいかわいい絵文字たちとおさらば👋になってしまいます。
プロジェクト固有のbundler設定(プライベートなgemで使うcredentialなど)の保存先に<root>/.bundle
フォルダを使う場合は、BUNDLE_APP_CONFIG
が必要です。デフォルトのRubyイメージではこの変数が定義されていて(#129)、bundlerがローカル設定にフォールバックしないようになっています。
また、bundle exec
を付けずにコマンドを実行できるよう、PATH
変数に<root>/bin
フォルダを追加することも可能です。これはデフォルトではオフにしてありますが、理由はマルチプロジェクト環境(Railsアプリ内でローカルのgemやエンジンを使う場合など)でコードが動かなくなる可能性があるためです。
🔗 Evil Martians流docker-compose.yml
Docker Composeは、コンテナ化された環境をオーケストレーションするツールで、これを用いてコンテナ同士を接続し、永続化ボリュームやサービスを定義できます。
以下は、データベースにPostgreSQL、バックグラウンドジョブの処理にSidekiqを用いた、Railsアプリケーションの典型的な開発環境のためのdocker-compose.ymlです。
version: '2.4'
services:
app: &app
build:
context: .dockerdev
dockerfile: Dockerfile
args:
RUBY_VERSION: '2.6.3'
PG_MAJOR: '13'
NODE_MAJOR: '11'
YARN_VERSION: '1.13.0'
BUNDLER_VERSION: '2.0.2'
image: example-dev:1.0.0
environment: &env
NODE_ENV: ${NODE_ENV:-development}
RAILS_ENV: ${RAILS_ENV:-development}
YARN_CACHE_FOLDER: /app/node_modules/.yarn-cache
tmpfs:
- /tmp
backend: &backend
<<: *app
stdin_open: true
tty: true
volumes:
- .:/app:cached
- rails_cache:/app/tmp/cache
- bundle:/usr/local/bundle
- node_modules:/app/node_modules
- packs:/app/public/packs
- .dockerdev/.psqlrc:/root/.psqlrc:ro
environment:
<<: *env
REDIS_URL: redis://redis:6379/
DATABASE_URL: postgres://postgres:postgres@postgres:5432
BOOTSNAP_CACHE_DIR: /usr/local/bundle/_bootsnap
WEBPACKER_DEV_SERVER_HOST: webpacker
WEB_CONCURRENCY: 1
HISTFILE: /app/log/.bash_history
PSQL_HISTFILE: /app/log/.psql_history
EDITOR: vi
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
runner:
<<: *backend
command: /bin/bash
ports:
- '3000:3000'
- '3002:3002'
rails:
<<: *backend
command: bundle exec rails server -b 0.0.0.0
ports:
- '3000:3000'
sidekiq:
<<: *backend
command: bundle exec sidekiq -C config/sidekiq.yml
postgres:
image: postgres:13
volumes:
- .dockerdev/.psqlrc:/root/.psqlrc:ro
- postgres:/var/lib/postgresql/data
- ./log:/root/log:cached
environment:
PSQL_HISTFILE: /root/log/.psql_history
POSTGRES_PASSWORD: postgres
ports:
- 5432
healthcheck:
test: pg_isready -U postgres -h 127.0.0.1
interval: 5s
redis:
image: redis:3.2-alpine
volumes:
- redis:/data
ports:
- 6379
healthcheck:
test: redis-cli ping
interval: 1s
timeout: 3s
retries: 30
webpacker:
<<: *app
command: ./bin/webpack-dev-server
ports:
- '3035:3035'
volumes:
- .:/app:cached
- bundle:/usr/local/bundle
- node_modules:/app/node_modules
- packs:/app/public/packs
environment:
<<: *env
WEBPACKER_DEV_SERVER_HOST: 0.0.0.0
volumes:
postgres:
redis:
bundle:
node_modules:
rails_cache:
packs:
このdocker-compose.ymlでは8つのサービスを定義しています。サービスを8つも定義しているのを不思議に思うかもしれませんが、その一部は単に他と共有する設定を定義しているだけで(app
やbackend
といった抽象サービス)、残りはアプリケーションコンテナを用いる特定のコマンド(runner
など)のためのものです。
私たちはDocker Composeのバージョン2を意図的に使っています。開発目的にはバージョン2が適しています。詳しくはDockerのissueをご覧ください(#7593)。
このアプローチでは、アプリケーションをdocker-compose up
で実行するのではなく、docker-compose up rails
のように、実行したいサービスを常にピンポイントで指定するようにしています。development環境ではWebpackerやSidekiqなどを全部立ち上げる必要はめったにないので、合理的です。
それでは各サービスを詳しく見ていくことにしましょう。
🔗 app
app
サービスの主な目的は、(上のDockerfile
で定義した)アプリケーションコンテナの構築に必要な情報をすべて提供することです。
build:
context: .dockerdev
dockerfile: Dockerfile
args:
RUBY_VERSION: '2.6.3'
PG_MAJOR: '13'
NODE_MAJOR: '11'
YARN_VERSION: '1.13.0'
BUNDLER_VERSION: '2.0.2'
context
ディレクトリは、Dockerのbuild contextを定義します。これはビルドプロセスで用いる一種のワーキングディレクトリであり、COPY
コマンドなどで用いられます。また、イメージがビルドされるたびにパッケージ化されてDockerデーモンに送信されるので、イメージのサイズはできるだけ小さくしておくのが望まれます。
ログや一時ファイルによってプロジェクトディレクトリのサイズが非常に大きくなるとビルドが遅くなる可能性があります。.dockerignoreファイルでサイズを減らすか、.dockerdevのような小さなディレクトリを指定しましょう。
私たちの設定ではDockerfileへのパスを明示的に指定しています。理由は、私たちはDockerファイルをプロジェクトのルートディレクトリに配置するのではなく、.dockerdev
という隠しディレクトリの中に他のすべてのDocker関連ファイルと一緒に配置しているからです。
前述したように、Dockerfileではargs
を用いて依存関係の正確なバージョンを指定しています。
ここでひとつ注意すべきは、イメージにタグ付けする方法です。
image: example-dev:1.0.0
Dockerを開発に用いるメリットのひとつは、設定の変更を自動的にチーム全体で同期できることです。これは、ローカルイメージ(引数や、イメージが依存するファイルでもよい)を変更するたびにローカルイメージのバージョン番号を常にアップグレードしておきさえすれば可能です。逆に最悪なのは、ビルドタグにexample-dev:latest
を使うことです。
イメージのバージョン番号が正しく管理されていれば、異なる2つの環境同士で余分な追加作業を一切行わずに済むようにもできます。たとえば、長期間実行するchore/upgrade-to-ruby-3
ブランチで作業している最中に、いつでもmaster
ブランチに切り替えて古いイメージや古いRubyを利用できます。しかもリビルド不要で。
重要: docker-compose.yml
内のイメージでlatest
タグを使うのは最悪です。
次に、共通の環境変数を追加します。この環境変数は、RailsやWebpackerなど複数のサービスで共有されます。
environment: &env
NODE_ENV: ${NODE_ENV:-development}
RAILS_ENV: ${RAILS_ENV:-development}
# 高速化のため、マウントしたボリュームにYarnキャッシュを保存
YARN_CACHE_FOLDER: /app/node_modules/.yarn-cache
ここでもいろんなことが行われていますが、1つ説明しておきたい点があります。
まずX=${X:-smth}
という構文についてです。これは「コンテナ内の変数X
で使う値は、ホストマシンに環境変数X
があればそれを使い、ない場合は指定の値を使う」という意味です。これによって、RAILS_ENV=test docker-compose up rails
のようにコマンドで別の環境を指定してサービスを実行できるようになります。
なお、environment
のフィールドがリスト形式(- NODE_ENV=xxx
)ではなく辞書形式(NODE_ENV: xxx
)になっていることにご注意ください。辞書形式にすることで、共通設定を再利用できるようになります(後述)。
他にも、コンテナ内で/tmp
フォルダにDockerのtmpfsマウントを用いるように指定することでスピードアップしています。
tmpfs:
- /tmp
🔗 backend
いよいよ本記事で一番美味しい部分にたどり着きました。
このbackend
サービスは、あらゆるRubyサービスで共有する振る舞いを定義します。
まずはvolumes:
を見てみましょう。
volumes:
- .:/app:cached
- bundle:/usr/local/bundle
- rails_cache:/app/tmp/cache
- node_modules:/app/node_modules
- packs:/app/public/packs
- .dockerdev/.psqlrc:/root/.psqlrc:ro
volumes:
リストの最初の項目「- .:/app:cached
」では、現在のワーキングディレクトリ(つまりプロジェクトのルートディレクトリ)をコンテナ内の/app
フォルダにマウントし、かつcached
戦略を用いています。このcached
という修飾子は、MacOSでのDocker環境の効率を高めるうえで重要なポイントです。cached
については別記事を書いていますので😉、本記事ではこれ以上は深堀りしません。詳しくはこちらの「公式ドキュメント」をご覧ください。
その次の行では、/bundle
という名前のボリュームに/urs/local/bundle
の内容を保存するようコンテナに指示しています(gemはデフォルトでここに保存されます)。私たちはこのようにして、gemのデータを永続化して複数の実行で使えるようにしています。docker-compose.yml
で定義されたすべてのボリュームは、docker-compose down --volumes
を実行するまで持続します。
以下の3行も、「DockerがMacだと遅い」という呪いをお祓いするために書かれています。私たちは、生成されたファイルをすべてDockerボリュームに配置することで、ホストマシンでディスク操作が重くなるのを回避しています。
- rails_cache:/app/tmp/cache
- node_modules:/app/node_modules
- packs:/app/public/packs
ポイント: macOSでDockerを十分高速に動かすには、ソースファイルを:cached
でマウントし、かつ、生成されたコンテンツ(アセットやbundleなど)の保存にはボリュームを使うこと。
末尾の3行では、特定のpsql
設定をコンテナに追加しています。私たちはほとんどの場合、コマンド履歴をアプリのlog/.psql_history
に保存することで永続化する必要があります。psql
をRubyのコンテナに追加している理由は、rails dbconsole
を実行するときに内部で使われるからです。
私たちが追加している.psqlrc
ファイルには、履歴ファイルを環境変数経由で指定できるようにするために以下の仕掛けが施されています。履歴ファイルへのパスをPSQL_HISTFILE
環境変数で指定できるようにし、利用できない場合は$HOME/.psql_history
にフォールバックします。
\set HISTFILE `[[ -z $PSQL_HISTFILE ]] && echo $HOME/.psql_history || echo $PSQL_HISTFILE`
環境変数について説明します。
environment:
<<: *env
REDIS_URL: redis://redis:6379/
DATABASE_URL: postgres://postgres:postgres@postgres:5432
WEBPACKER_DEV_SERVER_HOST: webpacker
BOOTSNAP_CACHE_DIR: /usr/local/bundle/_bootsnap
HISTFILE: /app/log/.bash_history
PSQL_HISTFILE: /app/log/.psql_history
EDITOR: vi
MALLOC_ARENA_MAX: 2
WEB_CONCURRENCY: ${WEB_CONCURRENCY:-1}
冒頭の<<: *env
で、共通の環境変数を「継承」しているのがポイントです。
DATABASE_URL
変数、REDIS_URL
変数、WEBPACKER_DEV_SERVER_HOST
変数は、Rubyアプリケーションを別のサービスに接続します。DATABASE_URL
変数はRailsのActive Recordで、WEBPACKER_DEV_SERVER_HOST
変数はRailsのWebpackerでいつでもサポートされます。ライブラリによってはREDIS_URL
変数もサポートします(Sidekiq)が、どのライブラリでもサポートされているとは限りません(たとえばAction Cableでは明示的に設定が必要です)。
私たちはbootsnapを用いてアプリケーションの読み込みを高速化しています。bootsnapのキャッシュはBudlerのデータと同じ場所に保存しています。理由は、このキャッシュに含まれている内容のほとんどがgemのデータだからです。つまり、たとえばRubyを別のバージョンにアップグレードする場合は、それらを一括廃棄するべきということです。
HISTFILE=/app/log/.bash_history
は、開発者のUXにとって重要な設定です。この設定によってbashの履歴が特定の場所に保管され、永続化されるようになります。
EDITOR=vi
は、たとえばrails credentials:edit
コマンドでcredentialファイルを管理するのに用います。
末尾の2つの設定であるMALLOC_ARENA_MAX
とWEB_CONCURRENCY
は、Railsのメモリハンドリングをチェックしやすくするためのものです。
他にbackend
サービスで説明すべきは以下の行だけです。
stdin_open: true
tty: true
この設定によって、サービスをインタラクティブ(TTYを提供するなどの対話的な操作)にできます。私たちの場合、たとえばRailsコンソールやBashをコンテナ内で実行するのに必要です。
これは、-it
オプションを付けてDockerコンテナを実行するのと同じです。
🔗 webpacker
webpacker
で言及しておきたいのはWEBPACKER_DEV_SERVER_HOST=0.0.0.0
という設定だけです。これによって、Webpack dev serverに「外部から」アクセスできるようになります(デフォルトではlocalhost
で実行されます)。
🔗 runner
このrunner
サービスの目的を説明するために、私がDockerを開発に用いるときの段取りについて説明させてください。
- 私はDockerデーモンの起動で以下のようなカスタム
docker-start
スクリプトを作って実行しています。
#!/bin/sh
if ! $(docker info > /dev/null 2>&1); then
echo "Docker for Macを開いています..."
open -a /Applications/Docker.app
while ! docker system info > /dev/null 2>&1; do sleep 1; done
echo "Docker準備OK!"
else
echo "Dockerは実行中です"
fi
- 次に、コンテナのシェルにログインするために、プロジェクトで
dcr runner
を実行します(dcr
はdocker-compose run
のエイリアス)。つまりdcr runner
は以下のエイリアスになります。
$ docker-compose run --rm runner
- 後はこのコンテナの中でほとんどの作業を行います(テストやマイグレーションやrakeタスクなど何でも構いません)。
以上でおわかりのように、私は何かタスクを1つ実行する必要が生じるたびにいちいちコンテナを1つ立ち上げたりせず、いつも同じ設定でやっています。
つまり私は、なつかしのvagrant ssh
と同じ感覚でdcr runner
を使っているのです。
私がこれをshell
と呼ばずにrunner
と呼んでいる理由はただひとつ、コンテナの中で任意のコマンドをrun
するのにも使えるからです。
メモ: このサービスをrunner
と呼ぶかどうかは好みの問題であり、(デフォルトのcommand
(/bin/bash
)は別としても)web
サービスと比べて何ひとつ目新しい点はありません。つまり、docker-compose run runner
はdocker-compose run web /bin/bash
と完全に同じです(ただし短い😉)。
🔗 ヘルスチェック
Railsでdb:migrate
などのコマンドを実行するときには、DBが起動していてコネクションを受け付けられる状態にしておきたいものです。依存するサービスが起動するまで待つようにDocker Composeに指示したい場合は、healthcheck
が使えます。
既にお気づきかと思いますが、depends_on
定義に書かれているのは単なるサービスリストではありません。
backend:
# ...
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
postgres:
# ...
healthcheck:
test: pg_isready -U postgres -h 127.0.0.1
interval: 5s
redis:
# ...
healthcheck:
test: redis-cli ping
interval: 1s
timeout: 3s
retries: 30
メモ: ヘルスチェックはDocker Composeファイルフォーマットv2.1以降でのみサポートされています。私たちがヘルスチェックを開発環境でしか使っていない理由はこれです。
🔗 おまけ: Evil Martians特製のdip.ymlについて
- リポジトリ: dip.yml
Docker Compose式のやり方がまだ難しいとお思いの方に、Dipというツールをご紹介します。これは開発者がスムーズなエクスペリエンスを得られるようにと、Evil Martiansのあるメンバーがこしらえたものです。
dip.ymlは、複数のcomposeファイルを使い分ける場合や、プラットフォームに依存する複数の設定を使い分ける場合に特に便利です。dip.ymlはそれらをまとめて、Dockerでの開発環境を管理する一般的なインターフェイスを提供できるからです。
dip.ymlについては別記事にて詳しく説明しようと思います。どうぞご期待ください!
追伸
本記事のtipsを共有してくれたSergey PonomarevとMikhail Merkushinに感謝いたします🤘。
元記事のトップ画像のクレジット: © NASA/JPL-Caltech, 2009
🔗 原文Changelog
- 1.1.3 (2021-03-30)
- minimagic gemのライセンス問題緩和のためDockerfileを更新(#35)
- docker-compose設定の環境変数を辞書形式に変更(#6)
- 1.1.2 (2021-02-26)
- 依存関係のバージョンを更新(#28)
- Aptfileにコメントを書けるようになった(#31)
- Dockerfile内のAptfileへのパスを修正(#33)
- 1.1.1 (2020-09-15)
- .dockerdevディレクトリをプロジェクトディレクトリではなくビルドコンテキストとして使用(#26)
- 1.1.0 (2019-12-10)
- Rubyのベースイメージをslimに変更
- Rubyバージョン用のDebianリリースを明示的に指定し、busterにアップグレード
- bundlerのパスを
/bundle
からbundler標準の/usr/local/bundle
に変更 - Docker Composeのファイル形式はv2.4を使用
postgres
サービスとredis
サービスにヘルスチェックを追加
訳注
以下のスライドも合わせて読むことで、より理解が進むと思います。
訳注
本記事の原文が全面的に更新されたので、以下の更新版を公開しました。今後は以下をご参照ください。