Ruby on Rails 7.1 で自動生成された Dockerfile を確認してみよう！

## はじめに

前回は、`Dockerfile` の自動生成そのものについて、`rails new` を実行する際のオプションや実行環境による違いを確認してみましたが、

https://qiita.com/normalsalt/items/af06ecf39da848157e4f

今回は、実際に生成されたファイルの中身を見ていきたいと思います。

というか、前回の記事は、もともと書くつもりのなかった（ 単一の記事にする予定ではなかった ）ものなので、実は、ここからが本番です。

## `Dockerfile` の生成方法について

詳しくは、上の記事を見てみて欲しいのですが、今回は、次のようなコマンドで生成された `Dockerfile` を確認していきます。

```bash
rails new . --database=postgresql --css=bootstrap
```

つまり、データベースに PostgreSQL を、CSS フレームワークとして Bootstrap を、それぞれオプションで指定した場合に生成される `Dockerfile` が対象となります。[^dockerfile]

[^dockerfile]: 要するに、[前回の記事](https://qiita.com/normalsalt/items/af06ecf39da848157e4f) で作成した `Dockerfile` を、そのまま利用するということです。

ちなみに、前回の記事で説明した通り、Node と Yarn のバージョンについては、実行環境に依存するようですが、`Dockerfile` としては、単に環境変数の値が変わるだけですね。

## `syntax` の確認

さて、まずは、1行目から見ていきましょう。

と言っても、もちろん、丁寧に説明しすぎてもアレなので、ものによっては、簡単に触れるだけに留めておきます。

```Dockerfile
# syntax = docker/dockerfile:1
```

これは、`Dockerfile` の構文の「 バージョン1 」の、最新の安定リリースを、常に指すようになっているそうです。[^syntax]

[^syntax]: https://docs.docker.com/build/dockerfile/frontend/

もし「 バージョン2 」がリリースされたりしたら、この辺の事情も変わってくるかもしれませんが、とりあえずは、そっとしておけばいいでしょう。

## ベース環境の確認

次に、後で説明するビルド用の環境と、最終的に利用することとなるイメージを作成するためのファイナルステージ環境の、両方で `base` となる環境を構築しているのが、3~14行目になります。

まぁ、そんなに大袈裟なものでもなくて、親イメージ（ すなわち Ruby [^ruby] のバージョン ）や、その他、共通する設定などを指定しているだけですね。

[^ruby]: https://hub.docker.com/_/ruby

```Dockerfile
# Make sure RUBY_VERSION matches the Ruby version in .ruby-version and Gemfile
ARG RUBY_VERSION=3.2.2
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"
```

ただ、ここで、作業ディレクトリ（ `WORKDIR` ）が `/rails` に設定されているのは、最低限、覚えておいた方がいいでしょう。

また、本番環境用に、いくつか Bundler 関連の環境変数も指定されています。[^bundler]

[^bundler]: https://bundler.io/v2.5/man/bundle-config.1.html

- `BUNDLE_DEPLOYMENT`：`Gemfile` の変更を禁止に
- `BUNDLE_PATH`：gem が配置される場所
- `BUNDLE_WITHOUT`：インストールしない gem（ グループで指定 ）

そのため、この `Dockerfile` を参考にして、開発環境も作ろうと思っている場合などには、ご注意ください。

## ビルド環境の確認

ここからは、最終的に生成される Docker イメージのサイズを小さくするために切り離されている、`build` 環境を見ていきましょう。[^build]

[^build]: https://docs.docker.com/build/building/multi-stage/

まずは、17~22行目ですが、`base` 環境を元に、後でビルドに必要となるパッケージを、色々とインストールしていますね。

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

# Install packages needed to build gems and node modules
RUN apt-get update -qq && \
    apt-get install --no-install-recommends -y build-essential curl git libpq-dev libvips node-gyp pkg-config python-is-python3
```

というか、主に gem と Node のインストールに必要な環境を整えてくれていて、以降、24~41行目で、実際にそれらのインストールが行われることとなります。

```Dockerfile
# Install JavaScript dependencies
ARG NODE_VERSION=18.15.0
ARG YARN_VERSION=latest
ENV PATH=/usr/local/node/bin:$PATH
RUN curl -sL https://github.com/nodenv/node-build/archive/master.tar.gz | tar xz -C /tmp/ && \
    /tmp/node-build-master/bin/node-build "${NODE_VERSION}" /usr/local/node && \
    npm install -g yarn@$YARN_VERSION && \
    rm -rf /tmp/node-build-master

# 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

# Install node modules
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile
```

ちなみに、Node モジュールのインストール手順が含まれているのは、`rails new` する際に `css` オプションを加えたからです。[^css]

[^css]: 他にも、Node 環境が必要となることが明らかなオプションを指定すると、同じような `Dockerfile` が生成されると考えられます。オプションを指定しなかった場合との差分については、[前回の記事](https://qiita.com/normalsalt/items/af06ecf39da848157e4f) もご参照ください。

一応、本番環境らしいものとしては、`yarn install` 時に `--frozen-lockfile` オプションが指定されていることが挙げられますかね。[^yarn]

[^yarn]: https://classic.yarnpkg.com/lang/en/docs/cli/install/

ともかく、これで、ビルドの実行環境は整ったので、43~50行目では、ローカルのファイルをコピーしてきて、Bootsnap [^bootsnap] とアセットのプリコンパイルを実行しています。

[^bootsnap]: https://github.com/Shopify/bootsnap

```Dockerfile
# 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
```

特に、本番環境のためのアセットのプリコンパイルについては、重要ですね。

詳細は、ぜひ Rails ガイドなどを参照してください。

https://railsguides.jp/asset_pipeline.html#production%E7%92%B0%E5%A2%83%E3%81%AE%E5%A0%B4%E5%90%88

## ファイナルステージ環境の確認

それでは、いよいよファイナルステージに突入です。

まず、53~59行目ですが、ビルド環境の時と同じ流れで、`base` 環境を元に、必要なパッケージをインストールしています。

```Dockerfile
# 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 libvips postgresql-client && \
    rm -rf /var/lib/apt/lists /var/cache/apt/archives
```

ここで、`postgresql-client` パッケージがインストールされているのは、`rails new` を実行する際に、`database` オプションで `postgresql` を選択したからですね。

そして、61行目から最終行までで、上で構築した `build` 環境の中から、必要なファイルをコピーしてきて、non-root ユーザーを作成するなど、起動のための準備を行います。

```Dockerfile
# 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 prepares the database.
ENTRYPOINT ["/rails/bin/docker-entrypoint"]

# Start the server by default, this can be overwritten at runtime
EXPOSE 3000
CMD ["./bin/rails", "server"]
```

以上、自動生成された `Dockerfile` の中身でした。

ちなみに、データベースの準備をしてくれるという `/rails/bin/docker-entrypoint` ファイルの中身は、こんな感じです。

```bash:/rails/bin/docker-entrypoint
#!/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 "${@}"
```

## おわりに

果たして、何かしら参考になりましたでしょうか？

正直な感想を言えば「 思ったより普通だったな 」という感じかもしれませんが、

まぁ、デフォルトとして自動生成してくれるだけで、なんとなく安心感があるというか、

少なくとも、どこからコピペしてきたのか分からないようなものと比べれば、断然マシですし、これだけ簡潔な内容だというのは、むしろ、評価すべきところかもしれません。

また、具体例をもとに、`Dockerfile` のベストプラクティスについて学ぶのにも、ちょうど良い機会となるのではないでしょうか？

https://docs.docker.com/develop/develop-images/instructions/

とは言え、今回は `Dockerfile` の内容を確認してみただけなので、これで、実際にコンテナが起動できるかどうかについては、実は、未知数です。

そして、とりあえず、次は、開発環境についても、Docker Compose の利用も含めて、考えてみたいところですね。[^next]

[^next]: [次回の記事](https://qiita.com/normalsalt/items/a26799924d52bda85c6f) で、方法を紹介しています。