Skip to main content

第26章:CIで速くする②:キャッシュマウントをCIでも活かす 🧙‍♂️⚡️

CI(たとえば GitHub Actions)で Docker ビルドを回してると、こんな悲鳴が出がちです👇😭

  • 「毎回 npm ci がフルで走って遅い…」🐢
  • 「BuildKit の RUN --mount=type=cache 入れたのに、次のCIで効いてない気がする…」😵‍💫
  • 「レイヤキャッシュは効いてるっぽいけど、依存DLは毎回…」📦💥

ここ、“レイヤキャッシュ” と “キャッシュマウント” が別モノなのが原因のことが多いです🧩


この章のゴール 🏁✨

CIでも RUN --mount=type=cache の恩恵を持ち越す ✅ 「ロックファイルが変わってレイヤキャッシュが壊れても、DL地獄を避ける」📦➡️⚡️ ✅ 速さが “安定して” 出る形にする(運ゲー卒業)🎲🚫


まず整理:レイヤキャッシュ vs キャッシュマウント 🧱🧠

ざっくり図にするとこうです👇

Dockerの速さに効くキャッシュは2種類あるよ

(1) レイヤキャッシュ 🧱
- Dockerfileの命令ごとに作られる層
- cache-to/cache-from(type=gha など) でCI間に持ち運べる

(2) キャッシュマウント 🧠
- RUN中だけ差し込まれる「作業用の永続フォルダ」
- 依存DLやコンパイルキャッシュに強い
- ただし…CIでは “そのままだと” 持ち越されないことがある

BuildKit のキャッシュマウントは「同じビルド内/同じキャッシュ環境で累積して効く」仕組みで、依存DLの再利用に強いです。(Docker Documentation) 一方で Docker 公式は、GitHub Actions のキャッシュ(type=gha)だけではキャッシュマウントが自動で保存されないと明言していて、回避策として専用アクションを案内しています。(Docker Documentation)


今回の基本戦略(結論)🧠💡

CIで速くするには 2段構えが最強です🔥

  1. **レイヤキャッシュ(type=gha)**で「Dockerfile全体の再ビルド」を減らす 🧱⚡️
  2. キャッシュマウントを actions/cache に退避して「依存DL」をさらに減らす 🧠📦

この “キャッシュマウント退避” をやるのが reproducible-containers/buildkit-cache-dance(通称:キャッシュダンス)です💃🕺 仕組みとしては「前回のビルドからキャッシュマウントの中身を取り出して、今回のビルド前に注入する」タイプ。(GitHub)


Step 1:Dockerfile 側(cache mount を入れる)🧑‍🍳🐳

ここでは npm版で例を書きます(pnpmでも考え方同じ!)😆

例:npm のDLキャッシュをマウントする 📦⚡️

## syntax=docker/dockerfile:1

FROM node:22-bookworm-slim AS deps
WORKDIR /app

## 依存ファイルだけ先に入れる(キャッシュ温存の王道)
COPY package.json package-lock.json ./

## npmのキャッシュ(/root/.npm)をRUN中に差し込む
RUN --mount=type=cache,target=/root/.npm \
npm ci

FROM node:22-bookworm-slim AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build

FROM node:22-bookworm-slim AS runner
WORKDIR /app
ENV NODE_ENV=production
COPY --from=build /app/dist ./dist
COPY package.json ./
CMD ["node", "dist/index.js"]

ポイントはこれ👇😊

  • npm ci のとき、/root/.npmDLしたtarball等が溜まる → 次回が速い
  • ロックファイルが変わって npm ci が再実行されても、DLが激減しやすい

Step 2:CIで “レイヤキャッシュ” を効かせる(まず土台)🏗️⚡️

Docker公式は、GitHub Actionsでは GHAキャッシュ backendが便利で推奨(ただし制限内で)という立て付けです。(Docker Documentation)

例:docker/build-push-action の cache-to/from(type=gha)🧱

name: ci
on:
push:
branches: [ "main" ]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Buildx
uses: docker/setup-buildx-action@v3
id: buildx

- name: Build (layer cache)
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: false
cache-from: type=gha
cache-to: type=gha,mode=max

ここまででも「そこそこ速い」になります🙂 でも… **キャッシュマウントはまだ“別枠”**なので、次で仕上げます💪🔥


Step 3:キャッシュマウントを CI 間で持ち越す(本題)🧙‍♂️🧠

Docker公式が案内している通り、GHAキャッシュはキャッシュマウントを自動保存しないので、回避策として buildkit-cache-dance を使います。(Docker Documentation)

やることはシンプル👇

  • actions/cache保存先フォルダ(例:cache-mount/)を復元
  • buildkit-cache-dance がその中身を ビルド前に注入し、ビルド後に 取り出して保存してくれる (GitHub)

完成形:GitHub Actions(レイヤ + マウント両方)🚀✅

name: ci
on:
push:
branches: [ "main" ]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up Buildx
uses: docker/setup-buildx-action@v3
id: buildx

# 1) マウントキャッシュの保存場所を復元(actions/cache)
- name: Restore cache-mount directory
uses: actions/cache@v4
id: cache-mount
with:
path: cache-mount
key: cache-mount-${{ runner.os }}-${{ hashFiles('Dockerfile', 'package-lock.json') }}

# 2) cache-mount を BuildKit の cache mount に注入(& 終了時に取り出し)
- name: Cache mounts (BuildKit cache dance)
uses: reproducible-containers/buildkit-cache-dance@v3
with:
builder: ${{ steps.buildx.outputs.name }}
cache-dir: cache-mount
dockerfile: Dockerfile
# cacheがヒットしてるなら、同じ内容を再アップロードしない(時間節約)
skip-extraction: ${{ steps.cache-mount.outputs.cache-hit }}

# 3) レイヤキャッシュ(type=gha)も併用
- name: Build (layers + mounts)
uses: docker/build-push-action@v6
with:
context: .
file: Dockerfile
push: false
cache-from: type=gha
cache-to: type=gha,mode=max

🔑 キー設計のコツ(超重要)🗝️

  • key には最低でも Dockerfile と lockfile を入れるのが安全👍

    • 依存が変わったのに古いキャッシュを使うと、微妙に事故る原因になります😵‍💫
  • 余裕があれば restore-keys で “近いキャッシュ” を拾うのもアリ(上級)🧠✨


どれくらい効くの?(期待値)📊⚡️

こういう挙動になります👇

  • ✅ ソース変更だけ:そもそも依存インストールが走らない(レイヤで勝ち)🧱✨
  • ✅ lockfile変更:npm ci は走るけど、DLが激減(マウントで勝ち)🧠⚡️

Docker公式も「キャッシュマウントは “レイヤが壊れてもDLを再利用できる”」という価値を推してます。(Docker Documentation)


ただし注意:GitHub Actions キャッシュの制限 🧯📦

GHAのキャッシュには「容量」「保持日数」の制限があります。 GitHubの案内では、デフォルトで 10GB 上限・保持 7日が基本で、ポリシーやプランで増やせる(増やすと課金が発生する場合あり)という整理です。(The GitHub Blog)

なので👇

  • キャッシュに 巨大なものを突っ込むとすぐ溢れて消えます💥
  • “効いてほしい場所” を絞るのがコツ(npmのDLキャッシュ等)🎯

よくある落とし穴集 😭🪤

1) マウント先が違ってて、空振りする 🎯❌

  • Dockerfileで target=/root/.npm にしてるのに 実際はユーザー変更していて ~/.npm が別…など 👉 コンテナ内のユーザー/ホームとセットで確認🕵️‍♂️

2) actions/cache の key が雑で、変なキャッシュを掴む 🗝️😵‍💫

👉 hashFiles('Dockerfile','package-lock.json') は最低ライン👍

3) キャッシュが肥大化して、いつの間にか消える 📦💣

👉 GHAキャッシュは上限があるので、増えすぎたら整理が必要🧹 (デフォルト制限の話は上の注意を参照ね)(The GitHub Blog)


🧪ミニ演習(手を動かそう)✍️🔥

演習1:効果を “見える化” する 👀⏱️

  1. まず キャッシュダンス無しでCIを2回回す
  2. 次に キャッシュダンス有りでCIを2回回す
  3. それぞれ npm ci の所要時間をメモ📒

演習2:lockfile変更で差を体感する 🔁⚡️

  1. 依存を1つ追加して package-lock.json を更新
  2. CIを回して npm ci が走るのを確認
  3. もう一度CIを回して “DLが減る感触” を確認✨

🤖AI活用(コピペでOK)💬✨

1) “どこをキャッシュすべきか” を診断してもらう 🩺

「このDockerfileで RUN --mount=type=cache を入れるなら、npm/pnpm/apt/go のどのディレクトリを target にすべき?理由つきで3案。あと GitHub Actions で持ち越す設定(buildkit-cache-dance + actions/cache)も提案して」

2) “キャッシュが効かない理由” を特定してもらう 🕵️

「CIログ(npm ci 前後の時間)と Dockerfile を貼るので、キャッシュが効いてない可能性が高い原因を優先度順に列挙して。確認コマンドもセットで」

3) キー設計を事故らない形にする 🗝️

「actions/cache の key/restore-keys を、依存更新時に安全に切り替わる設計にして。モノレポの場合の案も」


まとめ 🎁✨

  • 🧱 cache-to/from type=ghaレイヤキャッシュ(土台)
  • 🧠 RUN --mount=type=cacheDL地獄を減らす切り札
  • 💃 でもCIではそのままだと残らないことがある → buildkit-cache-dance で持ち越す(Docker公式の回避策)(Docker Documentation)
  • 📦 GHAキャッシュには容量/保持の制限があるので “絞る” のがコツ(The GitHub Blog)

次章(第27章)は「キャッシュが効かない典型パターン集 😭」なので、ここで作ったCIを土台にして “なぜ効かない?”の診断スキルを一気に上げていけますよ🔥🧠✨