はじめに
-
Next.js の Docker ビルドで、コード変更時のキャッシュ再利用が期待ほど効かない場面があった。
-
ビルドコンテキストにおける
COPY対象と、依存インストールの cache key を分けて考える必要があった。
要旨
確認したのは次の 2 点。
-
.dockerignoreで不要なファイルをビルドコンテキストから外す -
COPY package*.json ./→RUN npm ci→COPY . .の順にして、依存インストールのキャッシュ境界を分ける
確認した現象
-
Next.js フロントエンドの Docker ビルドで、外部キャッシュを使っていても一部の処理が再実行されることがあった。
-
ログを見ると、キャッシュが完全に無いわけではなく、特定の
COPY以降で cache miss していた。 -
同一コミットの再実行ではキャッシュが効いていたため、外部キャッシュの有無より、どの命令から cache miss しているかが重要だった。
Path context と build context
- Docker の build cache は命令ごとに判定される。
COPY/ADDでは、コピー対象ファイルのメタデータなどから cache checksum が計算される。 - 対象ファイルの内容やファイル集合が変われば、その
COPYは cache miss になる。
用語は次の意味で使用。
-
cache key: キャッシュを再利用できるか判定するための条件
-
cache checksum:
COPY/ADDなどで、対象ファイルの情報から計算される判定用の値 -
チェックアウト済みディレクトリを build context にすると、
.dockerignoreで除外していないファイルはCOPYの判定対象になり得る。 -
Git メタデータやローカル生成物のようにビルドに不要なファイルは、アプリコード以外の変化で cache key に影響し得る。
-
見直しの対象は、変わりやすい入力と安定しやすい依存インストールを同じ cache key に寄せないこと。
-
ビルドに必要ないメタデータやローカル生成物は build context から外しておく方が扱いやすい。
見直し: build context の整理
-
.dockerignoreで、ビルドに不要なファイルを除外する。 -
.dockerignoreとあわせて、Dockerfile 側でも依存インストールとアプリコードのCOPYを分ける。ソースコード変更時にnpm ciまで再実行範囲に入らないようにするため。 -
依存ファイルだけを先にコピーし、lockfile が変わらない限り
npm ciを再利用できる形にする。
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
- ソースコード変更時も
npm ciはpackage-lock.jsonが変わらなければ再利用されやすい。
確認結果
-
build context の整理後、コード変更時に依存インストールが再実行されるケースは減った。初回相当のビルドや同一コミット再実行よりも、コード変更ありのビルドで差が出た。
-
一方で、
.dockerignoreによる転送量削減だけでは体感できるほどの短縮は出なかった。このケースでは、COPY . .の対象と位置を見直し、時間のかかるレイヤーを再利用できるようにしたことの方が効いていた。 -
.dockerignoreを変更するとCOPY対象のファイル集合が変わるため、追加直後はそのCOPY以降で一度 cache miss が広がり得る。