Closed15

Rails7.1~で生成されるDockerfileを読んでみる

takaratakara

生成されたDockerfileはこちら

# syntax = docker/dockerfile:1

# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=2.7.6
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base

# Rails app lives here
WORKDIR /rails

# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development"

# Throw-away build stage to reduce size of final image
FROM base as build

# Install packages needed to build gems
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git libvips pkg-config

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
    bundle exec bootsnap precompile --gemfile

# Copy application code
COPY . .

# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile

# Final stage for app image
FROM base

# Install packages needed for deployment
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libsqlite3-0 libvips && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

# Copy built artifacts: gems, application
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails

# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash && \
    chown -R rails:rails db log storage tmp
USER rails:rails

ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000

CMD ["./bin/rails", "server"]

takaratakara

1行目から読んでいく

# syntax = docker/dockerfile:1

使用するsyntaxのバージョン指定。
公式によりdocker/dockerfile:1の使用が推奨されている。

私たちは docker/dockerfile:1 の使用を推奨します。これは、常にバージョン 1 構文syntax の最新 安定版stable リリースを示し、かつ、バージョン 1 のリリース・サイクルにおける「マイナー」と「パッチ」更新の両方も受け取れるからです。

https://docs.docker.jp/engine/reference/builder.html#builder-official-releases

早期アクセスの機能はlabsの指定で使えるらしい。

labs チャンネル
「labs」チャンネルが提供するのは、まだ stable チャンネルでは利用できない、 Dockerfile機能に対する早期アクセスです。Labs チャンネル・イメージは stable リリースと連携しています。同じバージョンに -labs 文字が付きます。たとえば、
docker/dockerfile:labs - labs チャンネルの最新リリース
docker/dockerfile:1-labs - stable チャンネルの dockerfile:1 と同じで、labs 機能が有効化
docker/dockerfile:1.2-labs - stable チャンネルの dockerfile:1.2 と同じで、labs 機能が有効化

takaratakara
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=2.7.6
FROM registry.docker.com/library/ruby:$RUBY_VERSION-slim as base

# Rails app lives here
WORKDIR /rails

使っているRubyのバージョンに合わせたイメージを取得したあと、作業ディレクトリを設定。
https://github.com/rails/rails/blob/d53ef2c18073a3a662edddd3fbbce32805e1135a/railties/lib/rails/generators/rails/app/templates/Dockerfile.tt#L4C1-L4C1

使っているイメージはRuby公式のやつかな?
https://hub.docker.com/_/ruby

takaratakara
# Set production environment
ENV RAILS_ENV="production" \
    BUNDLE_DEPLOYMENT="1" \
    BUNDLE_PATH="/usr/local/bundle" \
    BUNDLE_WITHOUT="development"

環境変数の設定。
BUNDLE_DEPLOYMENT="1" は知らなかった。

Gemfile.lockとGemfileが不一致だとエラーを出してくれる設定らしい。

deploymentモードは本番(またはCI)でのみ使用するものであり、developmentモードでオンにしてはならないとあります。
その理由には、先に述べたGemfile.lockが絡んできます。deploymentモードのbundlerは、Gemfile.lockだけを参照してgemをインストールします。そして、Gemfile.lockとGemfileで不一致があるとエラーで終了します。
--deploymentオプションでGemfile.lockだけを参照する理由は、Gemfileの内容でbundle installした結果が必ずしも一致するとは限らないからです。確実に動作するgemセットに一致するのはGemfile.lockなので、本番で何かの間違いでGemfileが更新されていたら、エラーで終了することでわかります。
https://techracho.bpsinc.jp/hachi8833/2016_08_29/25298

takaratakara
# Throw-away build stage to reduce size of final image
FROM base as build

マルチステージビルドの設定。
プリコンパイル後のアウトプットのみ流用して、ビルド後のイメージサイズを少なくするために初回のビルドをここから実装する。

takaratakara
# Install packages needed to build gems
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git libvips pkg-config

# Install application gems
COPY Gemfile Gemfile.lock ./
RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git && \
    bundle exec bootsnap precompile --gemfile
  • bundle installに利用するパッケージのインストール
  • bundle install
  • プリコンパイル

の実行。
プリコンパイル周りはAPIモード周りだと要らないかな?と思ったら、オプション次第で消えるようになっている模様。

RUN bundle install && \
    rm -rf ~/.bundle/ "${BUNDLE_PATH}"/ruby/*/cache "${BUNDLE_PATH}"/ruby/*/bundler/gems/*/.git<% if depend_on_bootsnap? -%> && \
    bundle exec bootsnap precompile --gemfile<% end %>

https://github.com/rails/rails/blob/d53ef2c18073a3a662edddd3fbbce32805e1135a/railties/lib/rails/generators/rails/app/templates/Dockerfile.tt#L45-L46

takaratakara
# Precompile bootsnap code for faster boot times
RUN bundle exec bootsnap precompile app/ lib/

開発環境だと読み込まれたファイルはbootsnapがキャッシュしてくれているけど、本番環境では最初からキャッシュ生成されていてほしいため、このコマンドで生成しているのかな?
この辺りまで意識出来ていないのでキチンと理解出来ていない...

In development environments the bootsnap compilation cache is generated on the fly when source files are loaded. But in production environments, such as docker images, you might need to precompile the cache.
To do so you can use the bootsnap precompile command.

https://github.com/Shopify/bootsnap#precompilation

takaratakara
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile

プリコンパイル時にダミーのsecret_key_baseを渡して、ビルドが不必要に止まらないようになっている?
わりと最近の機能らしい。
言われてみるとプリコンパイル時にこの辺りの情報はだいたい不要か🤔

productionでRAILS_MASTER_KEYを渡さなくてもassets:precompileを実行可能にした

https://techracho.bpsinc.jp/hachi8833/2023_01_25/126314#1-2

takaratakara
# Final stage for app image
FROM base

ここから次のステージのビルドの開始。
トータルのビルドステージは先程のイメージと合わせて2つのみ。

takaratakara
# Install packages needed for deployment
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y curl libsqlite3-0 libvips && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives

今回のビルドステージに必要なパッケージのインストール。
プリコンパイルは前のステージで終わらせているので、Rails本体の動作周りに必要なパッケージ群だと思われる。

takaratakara
# Copy built artifacts: gems, application
COPY --from=build /usr/local/bundle /usr/local/bundle
COPY --from=build /rails /rails

最初のステージでビルドしたイメージから、プリコンパイル後の成果物やgemなどをコピーしている。

takaratakara
# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash && \
    chown -R rails:rails db log storage tmp
USER rails:rails

railsを起動するためのユーザー・グループ設定。

takaratakara
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000

CMD ["./bin/rails", "server"]

Railsの起動コマンド投入。Dockerfileの記述は一旦ここまで。

docker-entrypointの中身は下記のようになっており、rails serverが実行された場合はrails db:prepareが動く仕組み。
ちなみにrails db:prepareはDBが存在しなければcreate~migration~seedまで実行し、存在する場合はmigrationのみ行ってくれるコマンドらしい。知らなかった🤔

#!/bin/bash -e

# If running the rails server then create or migrate existing database
if [ "${1}" == "./bin/rails" ] && [ "${2}" == "server" ]; then
  ./bin/rails db:prepare
fi

exec "${@}"
takaratakara

rails new api-testapp --api -d postgresql
ちなみにAPIモード&DB指定でrails newした所、下記のような違いが出た。

  • DB周りの設定が追加される
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential git libpq-dev libvips pkg-config
# libpq-devが増えた
apt-get install --no-install-recommends -y curl libvips postgresql-client && \
# libsqlite3-0 → postgresql-clientに変わった
  • プリコンパイルの実行コマンドが消える
# Precompiling assets for production without requiring secret RAILS_MASTER_KEY	
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
# ここが丸々消えた
このスクラップは6ヶ月前にクローズされました