LoginSignup
19
18

More than 3 years have passed since last update.

Docker 公式ドキュメントの Rails Quickstart 完全解説

Posted at

Docker の公式ドキュメントに Rails の Quickstart があります:
Quickstart: Compose and Rails | Docker Documentation

こちらのドキュメントを参考に Rails を始める方が増えており、
Tetatail で結構な件数の質問に回答しました。

この Quickstartは説明が不十分だったり間違いがあったりで
Rails に慣れてないと行間を読んで正しい手順を踏むことは難しいでしょう。

この記事で指摘している Quickstart の間違いなどは、すでに GitHub 上で PR を投稿しています。
Yukihiko shinoda/update quickstart rails by yukihiko-shinoda · Pull Request #11215 · docker/docker.github.io

ここで Rails の Quickstart についてまとめて解説しておきます。

この Quickstart を実施するために必要なツール

この解説を理解するために必要な基礎知識

この Quickstart を理解するためには、次の基礎知識が必要です:

理解するために必要ないツール・知識

  • Rails チュートリアル
    • Rails チュートリアルで解説されていることと、この Quickstart で得られる知識は、大部分が別の内容です
    • この Quickstart を理解する上で必要な Rails に関する知識はこの記事内で解説します

この Quickstart は何をするためのものなの?

この Quickstart ガイドでは、Docker Compose を使用して Rails / PostgreSQL アプリを設定および実行する方法を理解します。

この Quickstart はどんなことをしているの?

この Quickstart は次の手順を実施します:

  1. Define the project
    • Docker イメージのビルドに必要なファイルを作成します
  2. Build the project
    • 手順 1. で準備したファイルをもとに Rails の実行環境を起動して Rails を動かし新規 Rails アプリケーションを作成します
      • このとき自動的に Docker イメージがビルドされます
    • 新規作成した Rails アプリケーションを使い Docker イメージをビルドし直します
  3. Connect the database
    • Rails の実行環境とデータベースのサービスを一度に起動します
  4. View the Rails welcome page!
    • ブラウザで Rails の welcome page が表示されることを確認します

この Quickstart が理解しづらい原因

この Quickstart は手順 1. Define the project で準備するファイルやその内容が
相当な量であるにも関わらず、それらの目的についての解説が不十分であるためです。

  • 上記の手順 1. Define the project で準備するファイルの内容を最初からすべて理解することは難しいです
    • 準備するファイルの内容が次の目的を兼ねる内容になっており、どちらの目的のための内容なのか分かりづらいため
      • 手順 2. Build the project で行う新規 Rails アプリケーション作成のための内容
      • 最終的な Rails アプリケーション実行のための内容
  • 手順 2. Build the project で Rails の実行環境のためのイメージを再度ビルドし直しています
  • 手順 2. のコマンドが誤っています
  • Dockerfile に不要な内容が含まれています
  • docker-compose.yml
    • 誤っています
    • 不要な内容が含まれています

以降に、各手順で注意すべきポイントを解説します。

各手順の解説

Define the project

この手順では、Docker イメージのビルドに必要なファイルを作成します。

この手順を終えると、プロジェクトのディレクトリー内には次のようなファイルがあるはずです:

project/
+---docker-compose.yml
+---Dockerfile
+---entrypoint.sh
+---Gemfile
+---Gemfile.lock

ここで、初めてこの Quickstart を実施する人は
次の理由により混乱してしまうことでしょう:

  • ファイルの内容が誤っていたり、不要な内容が含まれているため
  • ここで準備するファイルやその内容が相当な量であるにも関わらず、それらの目的についての解説が不十分であるため

そこで、この記事では Dockerfiledocker-compose.yml の内容を次のように修正します:

Dockerfile
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]
docker-compose.yml
version: '3.8'
services:
  db:
    image: postgres
    environment:
      POSTGRES_PASSWORD: password
  web:
    build: .
    volumes:
      - .:/myapp
    ports:
      - "3000:3000"
    depends_on:
      - db

各ファイルの目的の整理

ここで準備するファイルは次の 2 つの目的のためです:

  • 手順 2. Build the project で行う新規 Rails アプリケーション作成のため
  • 手順 3. Connect the database で行う最終的な Rails アプリケーション実行のため

どのファイルがどの目的のためのファイルなのかは、次の通りです:

ファイル 目的
docker-compose.yml 手順 2, 3.
Dockerfile 手順 2, 3.
entrypoint.sh 手順 3.
Gemfile 手順 2.
Gemfile.lock 手順 2.

まず、手順 2. のために必要なファイルだけを準備できないの?

手順 2. のために必要なファイルだけを準備すると、次のようになります:

Dockerfile

手順 2. で必要な処理以外をコメントアウトします:

Dockerfile
FROM ruby:2.5
# RUN apt-get update -qq && apt-get install -y nodejs
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
# COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
# COPY . /myapp

# Add a script to be executed every time the container starts.
# COPY entrypoint.sh /usr/bin/
# RUN chmod +x /usr/bin/entrypoint.sh
# ENTRYPOINT ["entrypoint.sh"]

# # Start the main process.
# CMD ["rails", "server", "-b", "0.0.0.0"]
GemfileCOPY して bundle install を実行する理由

この後の手順 2. では rails new というコマンドを使い、
新規 Rails アプリケーションを作成します。

rails new コマンドを使うためには、Rails をインストールする必要があります。
Rails は「Gem パッケージ」という形式で配布されています。
「Gem パッケージ」とは、Ruby のプログラムを配布するための形式です。

参考: パッケージ - 意味・説明・解説 : ASCII.jpデジタル用語辞典

Gem パッケージをインストールするためには、
bundler という、Gem パッケージを一度にインストールするためのツールの
bundle install コマンドを使います。

bundle install コマンドは Gemfile というファイルを参照してパッケージをインストールします。

ここで出てきた命令一覧:

/myapp を新規作成して WORKDIR に指定する理由

ここで、/myapp ディレクトリーを作成して作業ディレクトリーとして指定しています。
これは、ベースイメージとして指定されている ruby:2.5 は、規定の作業ディレクトリーである / には
大切なディレクトリーがたくさんあり、
Rails アプリケーションのコードを展開したり、後述する「bind マウント」を行うと
様々な不具合が起きるためです。

ベースイメージの作業ディレクトリーを調べる方法は次の通りです:

FROM ruby:2.5
RUN pwd
$ docker-compose build
db uses an image, skipping
Building web
Step 1/2 : FROM ruby:2.5
 ---> a5c07a6f3bb6
Step 2/2 : RUN pwd
 ---> Running in 43f0381dd652
/

参考: Answer: Is it possible to show the `WORKDIR` when building a docker image?

ここで出てきた命令一覧:

Gemfile

Gemfile は Quickstart で提示されている内容をそのまま準備します:

Gemfile
source 'https://rubygems.org'
gem 'rails', '~>5'

Gemfile には、次のような情報を記述します:

  • Gem パッケージをどこからダウンロードしてインストールするか
  • インストールする Gem パッケージの一覧

この場合は標準の Gem パッケージ配信サーバーである https://rubygems.org から
Rails のバージョン 5 をインストールします。

参考: Bundler: The best way to manage a Ruby application's gems

docker-compose.yml

続いて、docker-compose.yml です。

手順 2. で必要な処理以外をコメントアウトします:

docker-compose.yml
version: '3'
services:
  # db:
  #   image: postgres
  #   environment:
  #     POSTGRES_PASSWORD: password
  web:
    build: .
    volumes:
      - .:/myapp
    # ports:
    #   - "3000:3000"
    # depends_on:
    #   - db
build 命令の解説

db サービスはここでは必要ありませんので、コメントアウトしました。
また、web サービス内で手順 2. で必要な設定は buildvolume のみです。

build は、先ほど作成した DockerfileGemfile を使ってイメージをビルドするために必要です。
build: . という指定は、
この web サービスのコンテナーのもととなるイメージとして、
docker-compose.yml があるディレクトリーに存在するファイルを
コンテキストとしてビルドしたイメージを使うことになります。

参考: build | Compose file version 3 reference | Docker Documentation

volumes 命令の解説

volumes は、この設定がないと、
手順 2. で rails new コマンドで作成する新規 Rails アプリケーションが、ホスト側のディレクトリーに残りません。

- .:/myapp という volumes の設定は
ホスト側の docker-compose.yml があるディレクトリーをコンテナー内のディレクトリツリーの /myapp に関連付けます。
こうすることで、手順 2. で実行する rails new コマンドで作成された新規 Rails アプリケーションが
ホスト側のディレクトリーに出力されるようになります。

このような volume の設定を bind といいます。

参考:
Use bind mounts | Docker Documentation
volumes | Compose file version 3 reference | Docker Documentation

まとめ

手順 2. のために必要なファイルだけを準備すると、次のようになります:

project/
+---docker-compose.yml
+---Dockerfile
+---Gemfile

Dockerfile の中で、手順 2. に不要なコードをコメントアウトしたので
Gemfile.lock は必要なくなりました。

Build the project

新規 Rails アプリケーションの作成

冒頭で実行する次のコマンドについて解説します:

docker-compose run web rails new . --force --no-deps --database=postgresql

このコマンドは間違っており、正しくは次のコマンドです:

docker-compose run --no-deps web rails new . --force --database=postgresql

上記のコマンドは docker-compose --no-deps run webrails new . --force --database=postgresql に分けて考えます。

docker-compose --no-deps run web

上記のコマンドは docker-compose.yml で定義した web サービスのコンテナーを起動して実行します。

docker run コマンドの書式は次の通りです:

docker-compose run [options] [-v VOLUME...] [-p PORT...] [-e KEY=VAL...] [-l KEY=VALUE...] SERVICE [COMMAND] [ARGS...]

--no-deps: Compose ファイル内の depends_on などで関係づけられたサービスを開始しません

つまり、--no-depsdocker-compose.yml 内の db サービスのコンテナーを起動しないための設定です。
(この時点ではデータベースは必要ないため)

docker-compose run コマンドは、
コンテナーを起動するためのイメージがまだビルドされていなければ、自動的にビルドを行います。

Dockerfile 内で、コンテナーを実行したときの CMD が定義されていますが、
このトレーニングのように docker-compose run コマンドでサービス名の後にコマンドを記述した場合は
CMD の内容は無視され、代わりにコマンドラインから入力した内容が実行されます。

rails new . --force --database=postgresql

このコマンドは新規 Rails アプリケーションを作成します。

rails new コマンドの書式は次の通りです:

rails new APP_PATH [options]

-f, [--force]: すでに存在するファイルを上書きします
-d, [--database=DATABASE] 選択したデータベース向けに初期設定します
選択肢: mysql / postgresql / sqlite3 / oracle / frontbase / ibm_db / sqlserver / jdbcmysql / jdbcsqlite3 / jdbcpostgresql / jdbc
省略時: sqlite3

このコマンドによって Gemfile を新規 Rails アプリケーション用の内容に上書きするので
--force オプションがない場合、Gemfile を上書きするかどうかの確認が表示されます。

イメージの再ビルド

手順 1. で、この解説記事に従って entrypoint.sh をまだ作成していない場合は、この時点で作成します:

#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /myapp/tmp/pids/server.pid

# Then exec the container's main process (what's set as CMD in the Dockerfile).
exec "$@"

また、Dockerfile の一部コメントアウトしていた部分を、次のようにコメントインします:

Dockerfile
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
# COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

まだコメントアウトしたままの命令がありますが、
これらは手順 4. を終えた後、Rails アプリケーションのイメージを作成するときに必要となる命令です。
手順 4. を終えた後で解説します。

次のコマンドを実行すると、イメージが再ビルドされます:

docker-compose build
Dockerfile の解説
ENTRYPOINT に Shell Script を設定し CMD でコマンドを指定する理由

Rails Server の二重起動を防ぐための排他ファイルである server.pid
何らかの原因で残ってしまった場合を想定して、
コンテナー起動時、Rails Server を起動する前に
entrypoint.sh 内で server.pid を削除する処理を実行しています。

ここで重要なのは、ENTRYPOINTCMD 命令を使った Dockerfile のデザインパターンで
イメージからコンテナーを起動したときの規定の動作を定義していることです。

ENTRYPOINTCMD を両方とも配列形式で与えると、すべての配列の要素を並べたコマンドが実行されます。

参考: Understand how CMD and ENTRYPOINT interact | Dockerfile reference | Docker Documentation

この例のように、ENTRYPOINT に Shell Script を設定し、
その Shell Script のコード内の最後で exec "$@" を呼び出す設計は
Dockerfile のデザインパターンとして広く使われています。
憶えておいて損はありません。

こうすることで、CMD で指定したコマンドを実行する前に何らかの初期化処理を実行することができるのです。

参考:

ここで出てきた命令一覧:

bundle install の前に Gemfile.lockCOPY する理由

このようにしないと、イメージを再ビルドしたときに、
Rails アプリケーションが開発時とは異なる動作や不具合を起こす可能性があります。
開発時とは異なるバージョンの Gem パッケージを使って動作することになる可能性があるためです。

Gemfile と Gemfile.lock は、どちらもインストールする Gem パッケージを管理するためのファイルですが、
それぞれ次のように扱います:

  • Gemfile
    • 開発者が目的に合わせて内容を編集します
    • プロジェクトが直接呼び出す Gem パッケージを定義します
    • 特別な理由がない限りバージョンを固定しません
  • Gemfile.lock
    • 開発者が直接編集せず、Gemfile の内容をもとにコマンドでコンパイルして更新します
    • プロジェクトが直接呼び出さない Gem パッケージも含めた、すべての必要な Gem パッケージが定義されます
    • すべてのパッケージがバージョンまで固定されています

bundle install コマンドを実行したとき、Gemfile.lock がなく、Gemfile のみが存在すると、
Gemfile で指定されている Gem パッケージの最新バージョンがインストールされます。

Gemfile.lock が存在し、Gemfile に特に更新がなければ
Gemfile.lock の内容に従って、開発時と同じバージョンの Gem パッケージがインストールされます。

参考:

nodejs をインストールする理由

Rails は JavaScript 実行環境である Node.JS を利用しており、
nodejs をインストールしていないと、Rails Server 実行時に次のようなエラーが発生します:

web_1  | /usr/local/bundle/gems/execjs-2.7.0/lib/execjs/runtimes.rb:58:in `autodetect': Could not find a JavaScript runtime. See https://github.com/rails/execjs for a list of available runtimes. (ExecJS::RuntimeUnavailable)
どうして再ビルドが必要なの?

rails new コマンドで上書きされた Gemfile には、
Rails 本体の他にも Rails アプリケーションを動作させるために必要な多くのパッケージが記載されています。

しかし、先ほどの docker-compose run コマンドでビルドされたイメージには、
Rails 本体しかインストールされていません。
これは、docker-compose run コマンドでビルドを行ったときの Gemfile には Rails しか記述されていなかったためです。

なお、ここで次のような記述があります:

This, and changes to the Gemfile or the Dockerfile, should be the only times you’ll need to rebuild.

この意味についてはすべての手順を終えた後で解説しますので、
学習効率を考慮して、ひとまず次の手順に進むことをお奨めします。

Connect the database

ここでは 2 つの設定を行っています:

  • Rails アプリケーションのデータベース接続設定を db サービスに接続できるように変更
  • db サービスに Rails アプリケーションが利用するデータベースを新規作成

Rails アプリケーションのデータベース接続設定を db サービスに接続できるように変更

手順 2. Build the project で生成された config/database.ymldefault: &default に次の要素を追加します:

config/database.yml
  host: db
  username: postgres
  password: password

この設定を行わずに docker-compose up を実行しても、ブラウザーでアクセスすると次のようなエラーが表示されます:

PG::ConnectionBad
could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

2020-08-06_13h03_31.png

このエラーメッセージは、Rails アプリケーションが
ローカルに PostgreSQL が起動している前提で接続しようとして失敗していることを示しています。

この場合、config/database.yml を更新してブラウザーを再読み込みするだけではエラーは解消しません。
これは、Rails がデータベース接続設定を読み込むのは Rails Server の起動時であるためです。
データベース接続設定を読み込み直すためには、
ターミナルで Ctrl + C を入力してコンテナーを停止し、docker-compose up を再度実行します。

db サービスに Rails アプリケーションが利用するデータベースを新規作成

docker-compose.yml があるディレクトリーでもう 1 つターミナルを開き、次のコマンドを実行します:

docker-compose run web rake db:create

この設定を行わずに docker-compose up を実行しても、ブラウザーでアクセスすると次のようなエラーが表示されます:

ActiveRecord::NoDatabaseError
FATAL: database "myapp_development" does not exist

2020-08-06_13h06_15.png

View the Rails welcome page!

次のような画面が表示されましたでしょうか?:

2020-08-06_14h41_27.png

この後の開発フローについて

Stop the application

この Quickstart では、アプリケーションを停止するときは、docker-compose down の利用が奨められています。
この記事に従って docker-compose.yml を修正した場合、
docker-compose down を実行すると、データベースの内容にアクセスできなくなります。

データベースの内容を保持したいときは、
docker-compose up を行ったターミナルで Ctrl + C を入力するか、
別のターミナルで docker-compose stop を実行すると、次に起動したときもデータベースの内容は保持されます。

基本的には、開発のためのデータを保持する必要がある場合は PC 内に残すのではなく、
factory_bot をはじめとする
データベースフィクスチャーを定義するための Gem パッケージを使ってコードに残します。
データの再利用の必要性を感じたら、コードに残す習慣をつけましょう。

参考: テストフィクスチャとは何? Weblio辞書

Rebuild the application

ここでは、イメージの再ビルドが必要な場合について述べられています。
ここで述べられている「イメージの再ビルドが必要な場合」とは、
「開発者が開発中に再ビルドを行うこと」を指しており、
「本番環境にデプロイするためのイメージをビルドすること」は指していません。

このことを理解するために、まずは「本番環境にデプロイするためのイメージをビルドすること」を考えてみます。

本番環境にデプロイするためのイメージのビルド

手順 1. Define the projectDockerfile コメントアウトしていた場合は、COPY . /myapp をコメントインします:

Dockerfile
FROM ruby:2.5
RUN apt-get update -qq && apt-get install -y nodejs
RUN mkdir /myapp
WORKDIR /myapp
COPY Gemfile /myapp/Gemfile
COPY Gemfile.lock /myapp/Gemfile.lock
RUN bundle install
COPY . /myapp

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT ["entrypoint.sh"]

# Start the main process.
CMD ["rails", "server", "-b", "0.0.0.0"]

COPY . /myapp は Rails アプリケーションのコードをビルドしたイメージに含めます。
本番環境では GitHub などのバージョン管理リポジトリーからコードをデプロイするのではなく、
Docker Hub などの Docker レジストリーから事前にビルドしたイメージをデプロイすることを想定しています。

参考: Docker Registry | Docker Documentation

Gemfile, Gemfile.lock と一緒に一度に COPY してはいけないの?

Gemfile, Gemfile.lockCOPY を分けているのは、bundle install に時間がかかるためです。

Docker イメージのビルドは、命令毎に結果がキャッシュされるので、
変更の可能性が低い命令、実行に時間がかかる命令は
なるべく Dockerfile の上に記述した方が再ビルド時の効率が良くなります。

COPY . /myapp 以降の命令はそれほどビルドに時間がかかりません。
もし、/myappGemfile, Gemfile.lock と一緒に一度に COPY していた場合、
COPY . /myapp 以降の命令を再ビルドしたいときであっても、
Rails アプリケーションのコードを更新していると bundle install が実行されることになり
ビルドに時間がかかってしまいます。

開発者が開発中に再ビルド

Quickstart では、手順 2. Build the project
この Docker を使った Rails 開発のワークフローに関する重要なことが述べられていまました:

This, and changes to the Gemfile or the Dockerfile, should be the only times you’ll need to rebuild.

これは、日本語訳すると、大体次のような意味です:

このときと、Gemfile もしくは Dockerfile を変更したときだけが、再ビルドが必要なタイミングとなるはずです。

これは、docker-compose.ymlvolume/myapp をホストから bind マウントしているので
開発のためにコンテナーを起動したとき、イメージに含めた Rails アプリケーションのコードではなく
ホスト側の最新の Rails アプリケーションのコードが実行されるためです。

Q & A

Docker 公式ドキュメントの Quickstart に関する質問

rails new コマンドでファイルが生成されたログが出力されるのに、実際にファイルが生成されません

Windows 10 でバージョンが 1709 までだと、
Docker Compose で相対パスを指定したときに想定通りにマウントされない不具合があるようです。
Windows Update の適用をおすすめします。
Ruby on Rails - dockerでRuby on Railsコンテナ起動時、Could not find public_suffix-4.0.5 in any of the sources エラー|teratail

ディレクトリーの名前を myapp 以外に変更すると rails new コマンド実行時にエラーが発生します

正確にすべてのディレクトリー名を変更すればエラーは発生しません。
どこかで書き換えを誤っていないか確認しましょう。
Ruby on Rails - ローカルディレクトリにrails newをしてもファイルが存在しない|teratail

Docker と Rails による Web サービス実行に関する質問

エラーメッセージの意味が何を指しているのかわかりません

複数のサービスを起動するときはコンテキストとなるディレクトリーを分けて設計していますか?
コンテキストとなるディレクトリーを分けると、原因の範囲が狭まり調査しやすくなります。
Ruby on Rails - Docker + Railsの No such file or directory エラーの解決方法について|teratail

パッケージが足りないエラーが表示されるので gem install しても直りません

Docker が動いているコンピューターに gem をインストールしていませんか?
gem のインストールはコンテナ内で行う必要があります。
Ruby on Rails - docker-compose upがうまくいかない|teratail

db サービス以外のデータベースに接続したいです

Rails の database.yml と環境変数でデータベース接続情報を変更して起動します。
MySQL - [Docker, Rails, Nginx, MySQL, EC2]docker-compose run app rails db:createするとエラーが出る。|teratail

multi-stage builds にしたら bundle: not found でイメージがビルドできなくなりました

ベースイメージが Ruby のイメージではないステージで rubybundler を使おうとしていませんか?
Ruby on Rails - "docker-compose build"実行時のエラー "Service 'web' failed to build: The command '/bin/sh -c bundle instal"|teratail

Gemfile はあるのに bundle install すると Could not locate Gemfile というエラーメッセージが表示されます

コマンドは Docker が動いているコンピューターで実行しているか、
それともコンテナの中で実行しているのかを確認し、
そこに Gemfile があるかを確認しましょう
GitHub - Dockerを用いたローカル環境構築で、Could not locate Gemfileが表示される|teratail

Dockerで起動したサーバーにlocalhost:3000でアクセスすると「このページは動作していません。」と表示されます

Docker で起動した web サービスのログを確認して、ログが増えるかどうかを確認します。
増えなければ、ファイアウォールの問題などの可能性があります。
MySQL - dockerで起動したサーバーにlocalhost:3000でアクセスしようとするが「このページは動作していません。」と返ってくる|teratail

19
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
18