「Waiting for Docker Build…」という悪夢
ログをたった1行修正するためだけに、Dockerビルドが完了するのを何十分も待ち続ける光景は、エンジニアにとって馴染み深いものでしょう。初めての方はDockerを基礎から学ぶところから始めるかもしれませんが、実務ではビルド時間の短縮が大きな課題となります。npm install や pip install が亀のような速さで進み、何百MBもの古いデータを何度もダウンロードするのを見るのは、本当にストレスが溜まるものです。
通常、私たちは package.json のコピー手順を分離してLayer Caching(レイヤーキャッシュ)を利用することでDockerfileを最適化します。しかし、この方法には致命的な弱点があります。小さなライブラリを1つ追加するだけで、Dockerはそのレイヤーが変更されたとみなし、キャッシュをすべて破棄してしまいます。その結果、システムは再び最初からすべてをダウンロードし直すことになります。
BuildKitと Mount Cache 機能は、この問題を根本的に解決するために生まれました。これにより、設定ファイルが変更された場合でも、パッケージマネージャー(npm、pip、go modなど)のキャッシュフォルダを複数のビルド間で保持できるようになります。筆者のプロジェクトでは、このDocker Imageの最適化技術を導入したことで、ビルド時間が8分から1分未満に短縮されました。
BuildKit的有効化 – 高パフォーマンスモードへの切り替え
BuildKitは、Dockerに標準搭載されている次世代のビルドエンジンです。Docker Desktop v2.4.0以降を使用している場合、デフォルトで有効になっています。Linuxユーザーや古いバージョンを使用している場合は、このエンジンを使用するように明示的に設定する必要があります。
現在のセッションで一時的に有効にするには、以下のコマンドを実行します。平衡:
export DOCKER_BUILDKIT=1
恒久的に設定する場合は、/etc/docker/daemon.json ファイルに以下の設定を追加します。
{
"features": { "buildkit": true }
}
ファイルを編集した後は、Dockerを再起動するのを忘れないでください。ビルド時にログ画面にステージが並列で表示され、よりプロフェッショナルな見た目になっていれば、設定は成功です。
言語別のMount Cache設定方法
その威力は、RUN 命令の --mount=type=cache フラグにあります。これはホストマシン上に一時ディレクトリを作成し、ビルド時にコンテナにマウントします。このディレクトリは、異なるビルド間でも保持されます。
1. Node.js (npm/yarn) の場合
Node.jsでは、node_modules ディレクトリとnpmのグローバルキャッシュが最も時間を消費します。実際にDockerでNode.jsアプリをデプロイする際、新規でダウンロードする代わりに、以下のようにnpmのキャッシュディレクトリをマウントします。
# 従来の方法: RUN npm install
# BuildKitによる最適化:
RUN --mount=type=cache,target=/root/.npm \
npm install
プロジェクトで Yarn を使用している場合は、ターゲットパスを変更します。
RUN --mount=type=cache,target=/usr/local/share/.cache/yarn \
yarn install
これで、package.json に新しいパッケージを追加しても、npmは差分のみをダウンロードするようになります。既存のライブラリはマウントされたキャッシュから即座に取得されるため、数百MBの帯域を節約できます。
2. Python (pip) の場合
pip install -r requirements.txt を実行するたびに待たされるのは苦痛です。以下の設定でpipのキャッシュを保持できます。
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
注意:root以外の一般ユーザーでDockerを実行している場合は、権限エラーを避けるために、target パスをそのユーザーのホームディレクトリに合わせて調整してください。
3. Go (Golang) の場合
Goは非常に優れたキャッシュメカニズムを持っていますが、デフォルトではイメージビルドのたびに削除されてしまいます。Goには、GOCACHE(コンパイル結果用)と GOMODCACHE(依存関係用)の2種類のキャッシュが必要です。
RUN --mount=type=cache,target=/go/pkg/mod \
--mount=type=cache,target=/root/.cache/go-build \
go build -o app main.go
2回目以降のビルドでは、Goが実際に変更されたコード部分のみを処理するため、驚くほどコンパイル速度が向上します。
キャッシュの確認と管理のコツ
この手法を導入する際は、ビルドログを注意深く観察し、意図通りに動作しているか確認することをお勧めします。インストール手順で CACHED という文字が表示されたり、実行時間が数分から数秒に短縮されたりしていれば、正しく設定されています。
マウントポイントの設定が複雑で混乱することもあります。筆者はよく toolcraft.app の JSON Formatter を使って、マニフェストファイルやDockerのJSONログを整形しています。これにより、重い拡張機能をインストールすることなく、マウントポイントやレイヤーを詳細に確認できます。
継続的なビルドの後にストレージをクリーンアップするには、以下のコマンドを使用します。
docker builder prune
このコマンドは、使用されていない古いキャッシュを削除し、サーバーのディスク容量を解放します。
CI/CD環境における重要な注意点
Mount Cacheは非常に強力ですが、「銀の弾丸(万能な解決策)」ではありません。以下の3点に注意してください。
- セキュリティ: 秘密情報や機密情報をキャッシュディレクトリに保存しないでください。これらはビルドホスト上に長期間残る可能性があります。
- CIシステム: GitHub Actionsでは、
docker/build-push-actionを使用し、cache-fromとcache-toにghaタイプを指定する必要があります。そうしないと、新しいRunnerでCIが実行されるたびに、Mount Cacheを利用するためのデータが失われてしまいます。 - 容量: プロジェクトの依存関係が多い場合、キャッシュは数GBから数十GBへと急速に肥大化する可能性があります。定期的なクリーンアップスクリプトを設定してください。
Dockerコンテナのセキュリティを考慮した運用やビルドの最適化は、開発者のストレスを軽減するだけでなく、CI/CDサーバーの運用コストを大幅に削減します。今すぐBuildKitを有効にしてMount Cacheを設定してみてください。その効果にきっと満足するはずです。
