はじめに
今回は既存のrailsアプリケーション環境をdocker化したので、そちらについてまとめていきます。dockerイメージの軽量化のためマルチステージビルドを採用しています。
マルチステージビルドについて
マルチステージビルドとは、複数のイメージを用いたビルド方法のことです。何やら難しそうに聞こえますが、内容としてはシンプルです。
-
FROM
を複数用意してそれぞれに名前を付ける(これが「ステージ」になります) - 各ステージは独立した一時的なイメージとして扱い、最終ステージだけが最終的な出力イメージとして保存される
- 後のステージから前のステージを
--from
を使って参照でき、前のステージからファイルをコピーできる
マルチステージビルドを用いることで、一つのDockerfileで複数のイメージをビルドすることができます。要はDockerイメージのビルドを効率的で、最適化されたものにするためのテクニックです。
マルチステージビルドを使用する大きな目的は以下の2つになります
- Dockerfileを読みやすく保守しやすくする
- 余分な部分は削ぎ落とし必要なものだけをコピーして、最終的なイメージサイズを小さくする(これにより、ビルド時間の短縮にもつながる!)
参考資料
- マルチステージビルドの利用 - docker docs
- マルチステージビルドの利用 - Docker-docs-ja 19.03
- Multi-stage build でNode.jsのインストールをちょっぴり効率化する
各種バージョン
- ruby : 3.0.2
- rails : 6.0.6.1
- node : 16.20.1
- yarn : 1.22.19
- postgresql : 12.15
rails環境のdocker化
ファイル構成
今回は既存のrailsプロジェクトをdocker化します。railsプロジェクトのルート直下に以下の5つのファイルを準備します(Gemfile, Gemfile.lockがすでにある場合はそちらを使ってもらっても大丈夫です)
- Dockerfile
- docker-compose.yml
- Gemfile
- Gemfile.lock
- .env
また、以下の3つのファイルを修正します
- Gemfile
- database.yml
- .gitignore
Dockerfile(追加)
DockerfileはDockerコンテナのイメージを構築するための手順を定義します。Dockerfileには、ベースとなるイメージの選択、新しいソフトウェアのインストール、環境変数の設定、ネットワークポートの開放、アプリケーションのコードのコピーなど、Dockerイメージの作成に必要な一連の手順が記述されます。
今回作成したDockerfileは以下のようになります。
FROM node:16-slim AS node
FROM ruby:3.0.2-slim
ENV YARN_VERSION 1.22.19
COPY --from=node /usr/local/bin/node /usr/local/bin/
COPY --from=node /opt/yarn-v$YARN_VERSION/ /opt/yarn/
RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs \
&& ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \
&& ln -s /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npx \
&& ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn \
&& ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg \
&& apt-get update -qq \
&& apt-get install -y \
build-essential \
libpq-dev \
shared-mime-info
WORKDIR /rails_app
COPY Gemfile Gemfile.lock package.json yarn.lock ./
RUN bundle install && yarn install
EXPOSE 3000
CMD ["rails", "s", "-b", "0.0.0.0"]
以下にコードの詳細をまとめました
(ちょっと長くなったので、必要な箇所だけ見ていただけたらと思います。。笑)
-
FROM node:16-slim AS node
でNode.jsを含むDockerイメージをビルドするステージです- このステージは以下のRubyのイメージをビルドするステージにnodeとyarnをコピーするために使います
- nodeという名前で参照できるように名前をつけています
-
FROM ruby:3.0.2-slim
でRubyのバージョン3.0.2を含むDockerイメージを作成する基礎となるイメージを指定します -
-slim
がついたイメージは、フルイメージの下位互換バージョンです- インストール済みのパッケージは使用頻度の高いものに限定され、その分軽量化されています
- 主に軽量のイメージが望ましい場合に使いますが、一部存在しないパッケージもあるので、正しく動作するかは十分なテストが必要です
- Dockerイメージ alpine,slim,stretch,buster,jessie等の違いと使い分け
-
ENV YARN_VERSION 1.22.19
でYarnのバージョンを環境変数として設定しています -
COPY --from=node /usr/local/bin/node /usr/local/bin/
とCOPY --from=node /opt/yarn-v$YARN_VERSION/ /opt/yarn/
で、nodeイメージからNode.jsとYarnをRubyイメージにコピーします -
RUN ln -s /usr/local/bin/node /usr/local/bin/nodejs ...
の部分でシンボリックリンクを作成し、必要な依存関係をインストールします- シンボリックリンクは、あるファイルへの参照のようなもので、それ自体は実際のファイルの内容を持っていないのですが、参照先のファイルへのリンクとして働きます
- ここでは、それぞれのコマンドラインツール(node, npm, npx, yarn, yarnpkg)へのシンボリックリンクを
/usr/local/bin/
に作成しています - これにより、上記のツールがどのディレクトリからでも直接アクセスできるようになります
-
usr/local/bin/
は通常、システムのパスに含まれています -
apt-get update -qq
でパッケージリストを更新し、apt-get install -y ...
で必要なパッケージをインストールします
-
WORKDIR /rails_app
で、以降の命令を実行する作業ディレクトリを指定します -
COPY Gemfile Gemfile.lock package.json yarn.lock ./
で、ホストマシンからDockerコンテナへ必要なファイルをコピーします -
RUN bundle install && yarn install
で、Rubyの依存関係(Gemfileに記載されたgem)とJavaScriptの依存関係(package.jsonに記載されたパッケージ)をそれぞれインストールします -
EXPOSE 3000
で、Dockerコンテナ内でアプリケーションが3000ポートでリッスンすることを示しています-
EXPOSE
命令自体はポートを開放するわけではありません - 実際のところ、EXPOSEはあくまでドキュメントとしての役割が大きく、人間やソフトフェアがそのDockerイメージを使う時にどのポートを開放すべきかを知るための情報を提供しています
-
-
CMD ["rails", "s", "-b", "0.0.0.0"]
で、Dockerコンテナが起動した時に実行するデフォルトのコマンドを設定します- このコマンドでRailsサーバーを起動し、すべてのIPからの接続を許可します
docker-compose.yml(追加)
docker-compose.ymlは、複数のDockerコンテナを定義し、一緒に起動、スケール、停止ができるようにするためのツールで、Docker Composeの設定ファイルです。
Dockerfileで個々のコンテナの設定
に、docker-compose.ymlは複数のコンテナの相互作用やアプリケーション全体の設定
に使われると理解するとわかりやすいです。
今回作成したdocker-compose.ymlは以下のようになります。
version: '3'
services:
db:
image: postgres:12
volumes:
- ./tmp/db:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
web:
build:
context: .
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
volumes:
- .:/rails_app
ports:
- "3000:3000"
depends_on:
- "db"
以下にコードの詳細をまとめました
-
dbサービス
-
image: postgres:12
で、DockerがDocker Hubからバージョン12のpostgresイメージをダウンロードし、これをもとにdbコンテナを作成します - 以下のコードでホストマシンの
./tmp/db
ディレクトリとコンテナ内の/var/lib/postgresql/data
ディレクトリをマウントします。これにより、データベースのデータが永続化され、コンテナが再起動または破棄されてもデータが保持されますvolumes: - ./tmp/db:/var/lib/postgresql/data
- 以下で、dbサービス(PostgreSQLのコンテナ)の環境変数を設定します
environment: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
-
-
webサービス
-
build:
でwebサービスのDockerイメージをビルドする際の設定をします-
context: .
でビルドコンテキストを指定します
-
-
command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
は、webサービスのコンテナが起動した時に実行されます- まず、
rm -f tmp/pids/server.pid
で不要なrailsサーバーのPIDファイルを削除し、新たにrailsサーバーを起動できるようにします - 次に、
bundle exec rails s -p 3000 -b '0.0.0.0'
でrailsサーバーをポート3000で起動します
- まず、
- 以下で、ホストのカレントディレクトリとコンテナ内の/rails_appディレクトリをマウントします。これにより、ホスト側でのソースコードの変更がコンテナに反映され、その逆も可能になります
volumes: - .:/rails_app
- 以下で、ホストのポート3000とコンテナのポート3000をマッピングします。これにより、ホストから
http://localhost:3000
でrailsアプリにアクセスできます。ports: - "3000:3000"
- 以下は、webサービス(railsサーバー)がdbサービスに依存していることを示しています。これにより、Docker Composeはdbサービスを先に起動し、その後でwebサービスを起動します。
- なお、
depends_on
はコンテナの起動順序を制御しますが、それが完全に準備(例えばデータベースが接続を受け付ける準備ができているなど)ができていることを保証するわけではないみたいです。。そのため、アプリケーション側でもデータベースなどのサービスへの接続が可能になるまで待機するロジックが必要な場合があります。depends_on: - "db"
-
.env(追加)
.envファイルは、アプリケーションの環境変数を定義するために使います。環境変数は一般的に、システムの設定情報や、データベースのパスワード、APIキー、その他の秘密情報など、環境に依存する値を格納する為に使います。ですので、今回はデータベースのパスワードはこちらに記載します。
POSTGRES_PASSWORD=password
.gitignore(修正)
.envファイルに記載している情報は秘密情報であり、これらを公開してしまうとセキュリティ上のリスクが生まれるので、.envファイル
を管理対象から外します。
# ▼以下を追記する
/.env
database.yml(修正)
デフォルトではSQLite用の設定になっているのですが、以下のようにPostgreSQLを使用する設定に変更します。passwordは.envに記載した環境変数を使用しています。
default: &default
adapter: postgresql
encoding: unicode
host: db
username: postgres
password: <%= ENV.fetch("POSTGRES_PASSWORD") %>
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: rails_app_dev
test:
<<: *default
database: rails_app_test
production:
<<: *default
database: rails_app_prd
- 以下の設定で、dbサービス名を指定することで、railsからPostgreSQLに接続できるようになっています。これは、
docker-compose.yml
で定義された各サービスは、Docker内部ネットワーク上で自動的に名前解決されるためです。つまり、railsはDocker内部ネットワーク上でdb
というホスト名でアクセス可能となります。host: db
Gemfile(修正)
# ▼以下の2行を削除
# Use sqlite3 as the database for Active Record
gem 'sqlite3', '~> 1.4'
# ▼以下の2行を追加
# Use pg as the database for Active Record
gem 'pg', '~> 1.2.3'
コンテナの起動
ファイルの作成と修正が完了したら以下の手順でコンテナを起動します。
- データベースの作成
docker-compose run web rails db:create
docker-compose run web rails db:migrate
- イメージをビルドしてコンテナを起動
docker-compose up
- 以下のURLにアクセスして画面が表示されることを確認
その他Tips
以下に動作確認時に使ったコマンドも記載しておきます。必要に応じて使ってみて下さい。
-
docker-compose up
を実行して、すべてのコンテナが起動しているか確認したいときはdocker-compose ps
を実行 - Dockerfileを更新したら、
docker-compose up --build
(buildしてrun)を実行 -
node:16-slim
イメージで使えるyarnのバージョンを確認したい時docker run node:16-slim yarn --version
-
node:16-slim
イメージで使われているnodeのバージョンを確認したい時docker run node:16-slim node -v
- postgresqlのバージョン確認したい時
docker-compose exec -it db bash
- dbコンテナ内で以下を実行
psql -U postgres -c 'SELECT version();'
参考資料
以下はマルチステージビルドに関する公式ドキュメント・資料になります(再掲)
- マルチステージビルドの利用 - docker docs
- マルチステージビルドの利用 - Docker-docs-ja 19.03
- Multi-stage build でNode.jsのインストールをちょっぴり効率化する
以下の動画は基本的なdocker、docker-composeを使ったrailsの環境構築で、とても分かりやすいです
docker-composeを使ってバックエンド、フロントエンド、データベースそれぞれをコンテナ化したSPA構成のアプリの環境構築をしたい方は、概要や全体像を理解するなら以下の動画がとても分かりやすいのでおすすめです!
dockerの基礎を学ぶのに以下のUdemy(Docker関連で最高評価のコンテンツです)を活用しました。