生成されるDockerfileのビルドパフォーマンスを2つの最適化で改善
Railsが生成するDockerfileテンプレートとテストフィクスチャに対し、Dockerレイヤーの生成コストを削減する2つの最適化が適用されました。実測で合計60〜70秒のビルド時間短縮が見込まれます。
背景
プロダクションビルドのプロファイリングにより、生成されるDockerfileに2つの非効率なパターンが特定されました。どちらもDockerのレイヤー差分計算に起因するオーバーヘッドで、Dockerfile.ttテンプレートとDockerfile.testフィクスチャの両方に影響していました。
主テンプレートのDockerfile.ttはすでにCOPY --chownによる最適化を採用していましたが、テストフィクスチャのDockerfile.testは古いパターンのままでした。本PRはその乖離を解消しつつ、node_modulesの削除処理も同時に改善しています。
技術的な変更
2つの独立した変更が行われており、それぞれ異なるDockerの非効率パターンに対処しています。
1. rm -rf node_modules をassets precompileレイヤーにマージ(Dockerfile.tt)
RUN rm -rf node_modules を独立したレイヤーとして配置すると、BuildKitは削除されたファイル群に対してフルのファイルシステム差分計算を行うコストが発生します。この処理だけで約13秒のオーバーヘッドがあることがプロファイリングで判明しました。
変更前:
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
<% if using_node? || using_bun? -%>
RUN rm -rf node_modules
<% end %>
変更後:
<% if using_node? || using_bun? -%>
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile && \
rm -rf node_modules
<% else -%>
RUN SECRET_KEY_BASE_DUMMY=1 ./bin/rails assets:precompile
<% end -%>
&& で同一のRUNステップに結合することで、BuildKitが計算するレイヤー差分は「precompile後にnode_modulesが存在しない状態」1回だけになります。Node.jsやBunを使用しない場合は従来通り単一のassets:precompileコマンドが生成されます。
2. chown -R を COPY --chown に置き換え(Dockerfile.test)
chown -R rails:rails は既存ファイルの所有権を変更するため、対象ファイル全体の差分レイヤーが生成されます。このパターンで約50秒のオーバーヘッドが発生していました。
変更前:
# Copy built artifacts: gems, application
COPY --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
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
変更後:
# Run and own only the runtime files as a non-root user for security
RUN useradd rails --create-home --shell /bin/bash
USER rails:rails
# Copy built artifacts: gems, application
COPY --chown=rails:rails --from=build "${BUNDLE_PATH}" "${BUNDLE_PATH}"
COPY --chown=rails:rails --from=build /rails /rails
COPY --chown はコピー時点で所有権を設定するため、別途chown -Rレイヤーを作成する必要がありません。また、COPYの順序がuseraddの後に移動し、USER rails:railsへの切り替えと論理的に一貫した構造になっています。
設計判断
レイヤー数を増やさず、既存のレイヤーに処理を統合するアプローチが一貫して採用されています。
rm -rf node_modulesの統合では、条件分岐(Node/Bun使用有無)をERBテンプレートレベルで処理することで、生成されるDockerfileにはすでに最適化されたRUNステップのみが含まれる設計になっています。COPY --chownへの移行では、chown -Rの削除に加えてCOPYステップの位置もuseraddの後に変更されており、「非rootユーザーとしてファイルをコピーする」という意図がDockerfileの構造として明示されています。
なお、主テンプレートDockerfile.ttはすでにCOPY --chownを正しく使用しており、テストフィクスチャDockerfile.testが追従していなかった点が今回修正されています。テンプレートとフィクスチャの一貫性が確保されたことで、今後の変更時に両者がずれるリスクも低減されます。
まとめ
本PRは、Dockerのレイヤー差分計算コストという具体的なオーバーヘッドを2箇所で解消しており、実測で合計60〜70秒のビルド時間削減を達成しています。変更はDockerfileの構造と生成ロジックの最小限の修正にとどまり、テンプレートとテストフィクスチャの一貫性も同時に改善されました。