Dockerイメージサイズを劇的に削減する方法【Rails7】

# はじめに
近年、アプリケーションの開発とデプロイのプロセスは、Dockerのようなコンテナ化技術によって大きく変革されています。Dockerコンテナの効率的な利用において、イメージサイズの最適化はデプロイの速度を速めるだけでなく、セキュリティリスクを低減し、ストレージコストを削減する上で重要な役割を果たします。

Railsアプリケーションの開発では、その豊富な機能性と便利さにより、依存するGemやアセットが増加し、結果としてDockerイメージのサイズが大きくなる傾向にあります。  

この記事では、RailsアプリケーションのDockerイメージサイズを劇的に削減するための具体的な手法を紹介し、デプロイの速度向上とコスト削減を実現する方法について解説します。

全ての検証コードはGitHubリポジトリに公開していますので、詳細は下記リンクからご確認ください。
[検証コードGitHubリポジトリ](https://github.com/mabo-matsu/rails7-docker-sample)

# 1. Rubyのベースイメージ比較
イメージサイズの比較を行った結果、下記の表のようになりました。
| Rubyイメージタイプ | イメージサイズ |
|-------------------|--------------:|
| 標準 (`ruby:3.2.3`)  | 1.22GB        |
| Slim (`ruby:3.2.3-slim`) | 744MB         |
| Alpine (`ruby:3.2.3-alpine`) | 445MB      |

Rubyイメージによって大きな差が見られました。  
特にAlpine版が標準版に比べて大幅にサイズを削減できることがわかります。  
しかし、各イメージは異なる用途や環境に最適化されているため、選択する際には考慮が必要です。

### イメージ選択の考慮事項
各イメージは、異なる用途や環境に最適化されています。適切なイメージを選択するためには、以下のポイントを参考にしてみてください。

- **標準版**: 全ての開発ツールとライブラリが含まれており、速やかに開発を開始できる環境を求める場合に適しています。
- **Slim版**: 不必要なコンポーネントを排除し、イメージサイズを削減しています。動作に必要な最小限のパッケージが事前に明確な場合に適しており、中規模から大規模のプロジェクトに最適です。
- **Alpine版**: 軽量化が極めて重要な場合や、リソースの制約が厳しい環境での利用に適しています。ただし、Alpine Linuxベースであるため、標準的なLinux環境と異なる挙動を示すことがあり、互換性や依存関係の問題に対する十分な検証が必要になります。

選択にあたっては、プロジェクトの要件、開発の便利さ、デプロイメント環境の制約などを総合的に評価して判断してください。  

# 2. Railsプロジェクト新規作成の最適化
Railsアプリの新規作成時に不要な部分をスキップすることで、プロジェクトが軽量化されないか検証してみました。
以下は今回利用した`rails new`コマンドです。
```bash
rails new . --force --asset-pipeline=propshaft --css=sass --database=mysql --skip-git --skip-docker --skip-action-mailer --skip-action-mailbox --skip-action-text --skip-active-job --skip-active-storage --skip-action-cable --skip-jbuilder --skip-bootsnap
```
`rails new`コマンドで利用可能なオプションは、プロジェクトのニーズに合わせて最適なセットアップを選択できるように、多岐にわたります。  
使用可能なすべてのオプションの一覧は、コマンドラインで`rails new --help`を実行することで確認できます。

### 検証結果
`rails new` コマンドで多くの機能をスキップして生成したにも関わらず、Dockerイメージのサイズに差は見られませんでした。スキップした機能に関連するGemがRails gemの依存関係としてインストールされるため、最終的にイメージサイズは通常の設定で作成した場合と同様に1.22GBとなりました。  

### プロダクションビルドでの潜在的な最適化
今回の検証では取り上げていませんが、プロダクションビルドでは以下の最適化が有効になることがあります。
- **.dockerignoreの設定追加**: test階層等のproduction環境が動作する上で不要なものをignoreすることでサイズを下げられることもある。
- **アセットプリコンパイル**: プロダクションビルド用にアセットをプリコンパイルし、開発環境特有のファイルをイメージから削除することで、イメージサイズの削減とパフォーマンスの向上が期待できます。

# 3. マルチステージビルドの利用
マルチステージビルドは、Dockerイメージのサイズを削減し、セキュリティを向上させる強力な手法です。  
この方法では、ビルドプロセスが複数のステージに分割され、最終的なイメージには必要なファイルのみが含まれるようになります。

実際にマルチステージビルドを適用した結果、以下のような改善が見られました。

- **Slim版**: 元のサイズから約35%減の482MBに削減。
- **Alpine版**: 元のサイズから約53%減の210MBに削減。

しかし、標準イメージに対しては、1.07GBまでの削減に留まりました。これは、標準イメージが多くのパッケージを含んでおり、特定のアプリケーションに必要なコンポーネントのみを選択する追加の作業が行われなかったためです。

### マルチステージビルドのメリット
マルチステージビルドを使用することで、ビルド時間の短縮、セキュリティリスクの低減、そしてデプロイプロセスの効率化が実現します。特にCI/CDパイプラインを通じた自動デプロイを行う場合、イメージサイズの削減は大きな利点となります。

今回の検証で使用したAlpine版のDockerfileは以下になります。
```
FROM ruby:3.2.3-alpine as base
ARG BUNDLE_ROOT=/usr/local/bundle
ENV APP_ROOT /myapp
WORKDIR $APP_ROOT
RUN apk add --update --no-cache mysql-dev tzdata gcompat git

FROM base as bundle
RUN apk add --update --no-cache build-base
COPY Gemfile $APP_ROOT/Gemfile
COPY Gemfile.lock $APP_ROOT/Gemfile.lock
RUN bundle install -j4 && rm -rf $BUNDLE_ROOT/cache/*

FROM base
COPY . $APP_ROOT
COPY --from=bundle $BUNDLE_ROOT $BUNDLE_ROOT
EXPOSE 3000
```

### 補足: プロダクション環境用イメージのマルチステージビルドについて
プロダクション環境用のイメージを構築する際にもマルチステージビルドを活用することで、さらにイメージサイズを削減することが可能です。アセットのプリコンパイルや開発環境特有のツールの排除など、プロダクション環境に特化した最適化を施すことにより、効率的なデプロイプロセスを実現できます。

# 主な発見点
- **Rubyのベースイメージ選択**は、イメージサイズに大きな影響を与えます。Alpine版は、サイズを大幅に削減できるが、依存性や挙動の違いに注意が必要です。
- **Railsプロジェクト新規作成時の最適化**は、Gemの依存関係により、想定されるほどイメージサイズの削減には繋がらないことが明らかになりました。
- **マルチステージビルド**の適用により、特にAlpine版とSlim版でイメージサイズが顕著に削減されました。これは、開発環境とプロダクション環境の両方での使用を検討する価値があります。

# 最後に
Dockerを使用したRailsアプリケーション開発では、イメージサイズの最適化が重要な課題です。本記事を通じて、イメージサイズの最適化に関する一歩を踏み出すきっかけになれば幸いです。
ただ、あくまでこの記事で紹介したイメージサイズの最適化手法は、一部に過ぎません。開発のプロセスを通じて、常により速く、よりセキュアで、より効率的な方法を模索することが求められると思うので、適した手法を探求していきましょう。