できない.dev

Docker build でキャッシュが効いて変更が反映されない

Docker build はレイヤーごとにキャッシュを再利用するため、COPY 元のファイル変更を検知させないと古いレイヤーが使われる。
--no-cache で全レイヤー無効化、--build-arg にビルド日時を渡す、COPY 順を依存とソースで分ける、で対処する。

#build#cache#dockerfile#buildkit

公開:

要約

Docker build は各命令を「レイヤー」として保存し、入力(命令文 + 参照ファイル)が一致する限り再利用する。
変更を反映したいのに古いレイヤーが使われる場合は、--no-cache で全無効化するか、--no-cache-filter <stage> で対象 stage だけやり直す。docker build --build-arg CACHEBUST=$(date +%s) で特定の ARG 以降を強制再実行する手法も常套。

よくある原因

  1. .dockerignore で対象ファイルを除外しており、変更があってもビルドコンテキストに入らないため、Docker からは「入力が変わっていない」と見える。
  2. RUN apt-get install ... のような命令は引数が同じ限りキャッシュされる。
    外側のミラーで apt リポジトリが更新されていても、Docker はキャッシュを使い続ける。
  3. COPY . . を依存インストールより前に書いてしまうと、ソースを 1 行直すたびに後段の npm install まで毎回やり直しになる。
    逆に依存ファイルだけ更新したつもりが、依存レイヤーの後ろの工程まで再評価されない。
  4. BuildKit の --mount=type=cache,target=/root/.cache を別ステージ間で共有していて、片方の更新が反映されないように見える。

解決策

1. 全レイヤーを無効化する

docker build --no-cache -t myimg .

確実だが当然遅い。
CI や検証ビルドのみで使うのが無難。

2. 特定 stage だけ無効化する(Docker 23+)

docker build --no-cache-filter builder -t myimg .

AS builder と書いた stage だけキャッシュを捨て、他は再利用する。
詳細は 公式ドキュメント (Cache invalidation)

3. 依存とソースで COPY を分ける

FROM node:20
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
RUN npm run build

package*.json を先に COPY して install を済ませてからソースをコピーすれば、ソースだけ変えたときは npm ci のレイヤーが再利用される。

4. ARG でキャッシュを意図的に壊す

ARG CACHEBUST=1
RUN curl -fsSL https://example.com/install.sh | sh
docker build --build-arg CACHEBUST=$(date +%s) -t myimg .

ARG 行の値が変わるとそれ以降の RUN がキャッシュミスになるので、外部 URL から毎回最新を取りたい場面で便利。

この記事は役立ちましたか?