「git push してから本番に反映されるまでが長い」「CI の Docker ビルドだけで毎回何分も待たされる」「ビルド回数に比例して CI の従量課金が積み上がる」——受託でシステム開発・運用を担っていると、こうした相談が定期的に持ち込まれます。きっかけになった Docker Buildを106秒→44秒、32秒→3秒に高速化した3つの改善(Zenn)(2026-06-08)は、複数コンテナを抱えるプロジェクトで 命令の並び替え・キャッシュ活用・不要ファイルの除外といった地味な改善だけで、ビルド時間を半分以下、ものによっては 10 分の 1 にまで縮めた実例でした。特別なツールを足したわけではなく、Dockerfile と CI の書き方を直しただけで大きな差が出ています。
受託の現場では、これは「ビルドが速いか遅いか」という技術自慢の話ではありません。ビルド時間はそのままデプロイのリードタイムであり、CI の従量課金であり、開発者が待たされる時間です。1 回 100 秒のビルドが 1 日に何十回も走れば、それは積もって開発体験とコストを蝕みます。受託で引き渡す立場では、「Dockerfile を最適化し、CI のキャッシュ設定まで含めて、保守しやすい状態でビルドを速くして渡せるか」が問われます。これまで バックエンドのメモリ削減でクラウドコスト最適化(GH Media) で扱った 実行時コストの削り方と接続して、本記事では 「CIビルド高速化支援」を 受託パッケージとして整理します。
なぜビルドが遅くなるのか — レイヤーキャッシュの仕組み
Docker のイメージは 命令(FROM / COPY / RUN …)ごとのレイヤーの積み重ねでできています。ビルド時、Docker は各命令の結果をキャッシュし、入力が前回と同じなら再実行せずキャッシュを再利用します。逆に、ある命令でキャッシュが無効になると、それ以降の命令はすべて再実行されます。ここが遅さの正体です。
| 観点 | 遅いビルド(よくある状態) | 速いビルド(最適化後) |
|---|---|---|
| 命令の並び | 変わりやすいソースコピーが先 | 変わりにくい依存インストールが先 |
| キャッシュ範囲 | レイヤーキャッシュのみ | + cache mount でDLキャッシュ保持 |
| ビルドコンテキスト | 不要ファイルも丸ごと送信 | .dockerignore で最小化 |
| イメージ構成 | ビルドツールも本番に同梱 | マルチステージで成果物だけ |
| CIでの再利用 | 毎回ゼロから | registry / GHA cache で共有 |
つまり 「依存インストールとソースコピーが同じ層に混ざっている」「不要ファイルまで送っている」「CI ではキャッシュが効いていない」といった構造があると、コードを一行直しただけで重い再ビルドが走ります。高速化とは、この再実行の連鎖を断ち切り、変わらない部分をキャッシュに固定する作業です。
高速化の具体手法
① 命令順序とキャッシュ層の分離
最も効くのは 「依存インストール」と「ソースコピー」を別の層に分け、変わりにくい依存を先に置くことです。package.json だけを先にコピーして依存をインストールし、その後にソース全体をコピーすれば、ソースを直しても依存のインストール層はキャッシュが効いたままになります。
# 改善前: ソースを全部コピーしてから install
# → ソースを1行直すたびに npm install が再実行される
COPY . .
RUN npm ci
# 改善後: 依存定義だけ先にコピーして install
# → ソース変更では install 層のキャッシュが効く
COPY package.json package-lock.json ./
RUN npm ci
COPY . .
この並び替えだけで、日常的な「ソースを直して再ビルド」のほとんどが依存インストールをスキップできます。冒頭の Zenn 記事の改善も、本質はこの「キャッシュが効く層の設計」です。
② BuildKit の cache mount で依存ダウンロードを保持
レイヤーキャッシュが無効化されても、パッケージマネージャのダウンロードキャッシュ(npm / pip / apt / Go モジュール等)だけは保持したい——それを実現するのが BuildKit の cache mount(--mount=type=cache)です。ビルド中だけ存在する専用のキャッシュ領域で、イメージには含まれないが次回ビルドで再利用されるのが肝です。
# syntax=docker/dockerfile:1
FROM node:22-slim
WORKDIR /app
COPY package.json package-lock.json ./
# npm のキャッシュディレクトリを cache mount で保持
RUN --mount=type=cache,target=/root/.npm \
npm ci
COPY . .
RUN npm run build
レイヤーキャッシュ(命令結果の保存)と cache mount(ダウンロード領域の保持)は 別々の仕組みで、両方を併用すると効果が重なります。①でキャッシュが無効化された場合でも、cache mount があればダウンロードからやり直さずに済みます。
③ マルチステージビルドと COPY —link
マルチステージビルドは 「ビルド用ステージ」と「実行用ステージ」を分け、最終イメージには成果物だけを持ち込む手法です。ビルドツールやソースを本番イメージから締め出せるので、イメージが小さくなり、転送・起動が速く、攻撃面も減ります。さらに COPY --link を使うと、コピー元のレイヤーが変わっても下流のキャッシュを壊しにくくなり、キャッシュヒット率が上がります。
# syntax=docker/dockerfile:1
# --- build stage ---
FROM node:22 AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN --mount=type=cache,target=/root/.npm npm ci
COPY . .
RUN npm run build
# --- runtime stage ---
FROM node:22-slim AS runtime
WORKDIR /app
ENV NODE_ENV=production
# 成果物だけを --link で持ち込む
COPY --link --from=build /app/dist ./dist
COPY --link --from=build /app/node_modules ./node_modules
CMD ["node", "dist/server.js"]
「ビルドに必要なもの」と「実行に必要なもの」を分けて考えるだけで、最終イメージのサイズとビルドの安定性が両立します。コンテナ運用そのものの効率化は Docker Gordon AIエージェントでコンテナ運用(GH Media) も併読してください。
④ .dockerignore の徹底
Docker はビルド開始時に ビルドコンテキスト(カレントディレクトリ一式)を丸ごとデーモンに送信します。.git / node_modules / ログ / ビルド生成物などを送っていると、送信だけで時間を浪費し、無関係なファイルの変更でキャッシュが無効化されます。.dockerignore で送る対象を絞るのは、効果の割に見落とされがちな一手です。
# .dockerignore の例
.git
node_modules
dist
*.log
.env
.env.*
Dockerfile
.dockerignore
特に .env 系を除外することは、後述の「秘密情報の混入」を防ぐ意味でも重要です。
⑤ CIでのキャッシュ共有
ローカルでは効いているキャッシュが、CI では毎回まっさらな環境のため効かない——これが「CI だけ遅い」典型です。BuildKit のキャッシュを registry にエクスポートする、あるいは **GitHub Actions のキャッシュバックエンド(type=gha)**を使うと、ジョブをまたいでビルドキャッシュを共有できます。
# GitHub Actions: BuildKit キャッシュを GHA に共有する例
- name: Set up Buildx
uses: docker/setup-buildx-action@v3
- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: registry.example.com/app:latest
cache-from: type=gha
cache-to: type=gha,mode=max
mode=max にすると 中間ステージのキャッシュまで保存され、マルチステージ構成でもヒット率が上がります。なお、cache mount の中身は GHA キャッシュにそのままは載らない点に注意が必要で、必要なら専用の救済手段を併用します。インフラ選定の前提整理は CloudflareとAWSのインフラ選定(GH Media) を参照してください。
受託で提供する「CIビルド高速化支援」5フェーズ
フェーズ 1: 計測・診断(1 週間)
- 現状の Dockerfile / CI 設定の棚卸し
- ビルド時間・キャッシュヒット率・イメージサイズの計測
- どの命令でキャッシュが壊れているかの特定
- 成果物: ビルド遅延の原因レポート + 改善優先度表
フェーズ 2: 設計(1 週間)
- レイヤー分離・cache mount・マルチステージの適用方針決定
- CI のキャッシュ共有方式(registry / GHA)の選定
- 改善後の目標ビルド時間・イメージサイズの設定
- 成果物: 最適化設計書 + 改善前後の見積もり
フェーズ 3: 実装(1〜3 週間)
- Dockerfile のリファクタリング(命令順序・cache mount・
COPY --link) .dockerignoreの整備とビルドコンテキストの最小化- CI ワークフローへのキャッシュ共有設定の組み込み
- 成果物: 最適化済み Dockerfile / CI 設定 + 実装標準ドキュメント
フェーズ 4: 検証・引き渡し(1 週間)
- 改善前後のビルド時間・課金・イメージサイズの比較計測
- キャッシュ無効化シナリオでの再現性確認
- 成果物: 効果測定レポート + 保守手順書
フェーズ 5: 継続保守(継続)
- 依存追加・ベースイメージ更新時のキャッシュ劣化監視
- CI キャッシュのヒット率の定点観測
- 新規サービス追加時のビルド設計支援
受託向け実装標準セット
| 用途 | 推奨 | 避ける |
|---|---|---|
| 依存とソース | 層を分け、依存を先に | COPY . . の後に install |
| DLキャッシュ | --mount=type=cache | 毎回ダウンロードし直し |
| イメージ構成 | マルチステージ + slim/distroless | ビルドツール同梱の肥大イメージ |
| コピー | COPY --link で安定化 | 巨大ディレクトリの一括コピー |
| ビルドコンテキスト | .dockerignore で最小化 | リポジトリ丸ごと送信 |
| CIキャッシュ | registry / type=gha,mode=max | キャッシュ未設定で毎回ゼロから |
どの案件に必要か / 不要か
| 必要な案件 | 優先度が低い案件 |
|---|---|
| CI のビルドが数分かかりデプロイが詰まる | ビルドが数秒で完結する小規模 |
| デプロイ頻度が高く待ち時間が積もる | デプロイが月数回程度 |
| CI の従量課金が膨らんでいる | 無料枠で十分収まる |
| イメージが巨大で転送・起動が遅い | イメージが既に軽量 |
| 複数サービスを並行ビルドしている | 単一・単純な構成 |
受託契約に書く6つの条項
| 条項 | 内容 | 顧客が確認すべきこと |
|---|---|---|
| 対象範囲 | 最適化するサービス/Dockerfile | 対象の境界 |
| 目標値 | ビルド時間/イメージサイズの目標 | 計測の定義 |
| CI前提 | 対象 CI と権限/キャッシュ環境 | 反映先の整合 |
| 秘密情報 | ビルド時の秘密の扱い | 混入防止の基準 |
| 引き渡し | Dockerfile / CI 設定 / 手順書 | 保守体制 |
| 継続保守 | キャッシュ劣化の監視 | 運用費用 |
価格モデル — CIビルド高速化パッケージ
| プラン | 金額 | 対象 | 内容 |
|---|---|---|---|
| 診断 | 20 万円〜 | 1 リポジトリ | 計測 + 原因レポート + 改善優先度 |
| 標準実装 | 80 万円〜 | 中規模 | Dockerfile 最適化 + CI キャッシュ設定 |
| 本格対応 | 160 万円〜 | 大規模 | + 複数サービス + マルチステージ全面再設計 |
| Lite 保守 | 3 万円〜 / 月 | 小規模 | ヒット率監視 + 軽微修正 |
| Standard 保守 | 10 万円〜 / 月 | 中規模 | + 新規サービスのビルド設計支援 |
顧客側 ROI 試算(CI ビルドを多用するチーム想定)
| 項目 | 最適化前 | 最適化後 | 差分 |
|---|---|---|---|
| ビルド時間 | 100 秒前後 | 数十秒〜数秒 | リードタイム短縮 |
| CI 従量課金 | ビルド時間に比例 | 大幅圧縮 | 月額コストの低減 |
| デプロイ回数 | 待ち時間で抑制 | 気軽に回せる | リリース頻度の向上 |
| 開発者の待ち | 都度発生 | ほぼ解消 | 開発体験の改善 |
| 年間効果 | — | — | 課金削減 + 開発速度向上 |
診断(20 万円〜)だけでも、「自分たちのビルドの、どこでキャッシュが壊れ、何秒削れるのか」を数字で可視化できること自体に価値があります。ビルド時間は毎日積み上がるコストなので、一度の最適化が長く効きます。
ハマりやすい5つの落とし穴
落とし穴 1: 不用意な命令でキャッシュが無効化される
COPY . . を依存インストールの前に置くと、ソースを直すたびに全部再ビルドされます。変わりにくいものを先、変わりやすいものを後に並べます。
落とし穴 2: 秘密情報をビルド時に混入させる
ARG や COPY で API キーや .env をイメージに焼き込むと 履歴に残って漏れます。.dockerignore で除外し、必要な秘密は --mount=type=secret で渡します。
落とし穴 3: CI とローカルでキャッシュ状態が違う
ローカルでは速いのに CI では毎回ゼロから——CI のキャッシュ共有を設定し、計測も CI 上で行って差を埋めます。
落とし穴 4: イメージを巨大なまま放置する
ビルドツールや不要パッケージを本番に同梱すると、転送・起動が遅く攻撃面も増えます。マルチステージで成果物だけに絞ります。
落とし穴 5: 過度な最適化で読めなくする
cache mount や条件分岐を詰め込みすぎると、誰も触れない Dockerfileになります。効果の大きい改善から入れ、保守できる範囲に留めます。
90日アクションプラン
| 週 | アクション |
|---|---|
| Week 1 | ビルド時間・キャッシュヒット率・イメージサイズの計測 |
| Week 2 | 原因特定 + 最適化方針と目標値の決定 |
| Week 3〜5 | Dockerfile リファクタ + .dockerignore 整備 + CI キャッシュ設定 |
| Week 6 | 改善前後の比較計測 + 手順整備 |
| Week 7〜13 | ヒット率の定点観測 + 新規サービスへの展開 |
まとめ — 「とりあえずビルドが通る」から「速くて保守できる状態で引き渡す」へ
Docker ビルドの高速化は、特別なツールではなく 命令順序・cache mount・マルチステージ・.dockerignore・CI キャッシュ共有という地味な積み上げで実現します。106 秒が 44 秒に、32 秒が 3 秒になるのは、魔法ではなく キャッシュが効く構造を設計し直した結果です。受託で引き渡す立場では、Dockerfile と CI を最適化し、効果を数字で示し、保守手順まで添えて渡す 「CIビルド高速化支援」が、デプロイのリードタイムと従量課金を同時に削る主力サービスです。本番の性能保証まで踏み込むなら k6 負荷テストで性能保証(GH Media) も併読してください。
弊社では診断 / 標準実装 / 本格対応 / Lite / Standard の各段階で本パッケージを提供しています。「CI のビルドが遅くてデプロイが詰まる」「ビルドの従量課金を減らしたい」「保守できる形で Dockerfile を直してほしい」というご相談は お問い合わせフォーム からお気軽にどうぞ。
Sources
- Docker Buildを106秒→44秒、32秒→3秒に高速化した3つの改善(Zenn 2026-06-08)
- Optimize cache usage in builds(Docker Docs)
- Cache management with GitHub Actions(Docker Docs)
- 高度な Dockerfiles: BuildKit とマルチステージビルド(Docker Blog)
- Docker Gordon AIエージェントでコンテナ運用(GH Media)
- k6 負荷テストで性能保証(GH Media)
- バックエンドのメモリ削減でクラウドコスト最適化(GH Media)
- CloudflareとAWSのインフラ選定(GH Media)