docker buildのキャッシュと.dockerignore設計|Dockerfileの命令順序でCIビルドを高速化する実践手順

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOMELinux技術 リナックスマスター.JP(Linuxマスター.JP)Docker > docker buildのキャッシュと.dockerignore設計|Dockerfileの命令順序でCIビルドを高速化する実践手順
「docker build が遅すぎる。ソースコードを1行書き換えただけで全依存パッケージを再インストールしている」——この問題、CIパイプラインを組んだことがある方なら必ず直面します。

原因の多くは Dockerfile の命令順序の誤りと .dockerignore ファイルの未設定にあります。Dockerのビルドキャッシュを正しく活用すると、変更のないレイヤーは再利用されるため、5分かかっていたビルドが30秒以下になることも珍しくありません。

この記事では、Dockerのビルドキャッシュの仕組みから命令順序の最適化、.dockerignoreの設計、BuildKitのキャッシュマウントまで、実務で即使えるノウハウを順番に解説します。RHEL 9.4 / Ubuntu 24.04 LTS + Docker 26.x で動作確認済みです。

この記事のポイント

・Dockerのキャッシュは命令が変わった行以降すべて無効化される
・変更頻度の低い命令(apt install)を上に、ソースのCOPYを最後に書く
・.dockerignoreでビルドコンテキストを削減し転送時間を大幅に短縮できる
・BuildKitの--mount=type=cacheでパッケージキャッシュを永続化できる


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
図解60P/登録10秒/解除も3秒 / 詳細はこちら

なぜDockerのビルドが遅くなるのか

Dockerのビルドは、Dockerfile の各命令を1行ずつ実行してレイヤーを積み重ねる仕組みです。各命令の実行結果はレイヤーとしてキャッシュされ、次のビルド時に同じ命令が同じ状態で実行されれば、実際の処理をスキップしてキャッシュから結果を取り出します。

Dockerのキャッシュには重要なルールがあります——ある命令でキャッシュが無効化されると、それ以降のすべての命令もキャッシュが使えなくなります。

たとえば、次のような Dockerfile では毎回 pip install が走ります。

# NG例:ソースコードを最初にコピーしてしまっている FROM python:3.12-slim WORKDIR /app COPY . . # ここでソースが変わるたびにキャッシュ無効 RUN pip install -r requirements.txt # 毎回フルインストールになる

COPY . . 命令はソースコードを丸ごとコンテナにコピーします。ソースを1行でも変えると COPY のキャッシュが無効化され、その後の RUN pip install まで再実行されます。requirements.txt が変わっていなくても毎回 pip install が走るのはこのためです。

Dockerfileの命令順序でキャッシュヒット率を上げる

1. NG例:変更頻度の高いCOPYを前に書く

前述のNGパターンを改めて確認します。ビルドのたびに時間がかかる原因になります。

# NG:ソースをまとめてコピーしてからインストールしている FROM python:3.12-slim WORKDIR /app COPY . . # ソース変更のたびにここでキャッシュ無効 RUN pip install -r requirements.txt CMD ["python", "app.py"]

2. OK例:requirements.txtだけ先にコピーしてインストールする

依存関係定義ファイル(requirements.txt)だけを先にコピーし、パッケージをインストールした後でソースコードをコピーします。

# OK:依存関係ファイルを先にコピーしてインストール FROM python:3.12-slim WORKDIR /app # requirements.txt だけ先にコピー(変更頻度が低い) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # ソースコードは最後にコピー COPY . . CMD ["python", "app.py"]

こうすることで、ソースコードが変わっても pip install のキャッシュは維持されます。依存関係が変わった時だけ pip install が再実行されます。

実際のビルド時間の差を確認した例です。

# 最適化前(ソースコードを変更した後の再ビルド) $ docker build -t myapp:ng . [+] Building 47.2s (7/7) FINISHED => [internal] load build context 0.0s => => transferring context: 1.24GB 12.3s => CACHED [1/3] FROM docker.io/library/python:3.12-slim 0.0s => [2/3] COPY . . 0.3s # キャッシュ無効 => [3/3] RUN pip install --no-cache-dir -r requirements.txt 33.8s # 再インストール => exporting to image 0.8s # 最適化後(ソースコードを変更した後の再ビルド) $ docker build -t myapp:ok . [+] Building 0.7s (8/8) FINISHED => [internal] load build context 0.0s => => transferring context: 13.4kB 0.0s => CACHED [1/4] FROM docker.io/library/python:3.12-slim 0.0s => CACHED [2/4] COPY requirements.txt . 0.0s # キャッシュ使用 => CACHED [3/4] RUN pip install --no-cache-dir -r requirements.txt 0.0s # キャッシュ使用! => [4/4] COPY . . 0.1s => exporting to image 0.4s

3. RUN命令のグループ化でキャッシュ粒度を調整する

apt の場合は、apt-get updateapt-get install を必ず同一の RUN 命令内にまとめます。別の RUN に分割すると、install 時に update のキャッシュが古くなって「パッケージが見つからない」エラーが発生することがあります。

# apt のベストプラクティス:update と install を同一RUNにまとめる RUN apt-get update && apt-get install -y \ curl \ git \ build-essential \ && apt-get clean \ && rm -rf /var/lib/apt/lists/*

末尾の apt-get cleanrm -rf /var/lib/apt/lists/* は同一レイヤー内に書くことでイメージサイズを削減できます。別の RUN に分けると、前のレイヤーにキャッシュが残ってしまいます。

.dockerignoreでビルドコンテキストを最小化する

1. ビルドコンテキストとは何か

docker build . を実行すると、カレントディレクトリの内容がビルドコンテキストとしてDockerデーモンに送信されます。Dockerfileのみがビルドに使われるわけではなく、指定したディレクトリ全体が転送されるのです。

# .dockerignore がない状態でのビルド $ docker build . [+] Building 18.4s (2/2) => [internal] load build context => => transferring context: 1.23GB # .git や node_modules が丸ごと転送されている

node_modules や .git ディレクトリが含まれると転送だけで数GBになることがあります。この転送時間がビルドの遅さの原因になっているケースは多く、.dockerignore を追加するだけで劇的に改善できます。

2. .dockerignoreの書式と除外パターン

プロジェクトルートに .dockerignore ファイルを作成します。書式は .gitignore と同じで、パターンを1行ずつ記述します。

# .dockerignore の例(プロジェクトルートに置く) # バージョン管理 .git .gitignore # 依存パッケージ(コンテナ内で再インストールするため不要) node_modules __pycache__ *.pyc *.pyo .venv venv/ # テスト・品質管理 .pytest_cache .coverage htmlcov/ tests/ # IDE・エディタの設定ファイル .idea/ .vscode/ *.swp # ビルド成果物 dist/ build/ *.egg-info/ # ローカル設定・シークレット .env .env.local *.pem *.key # Docker自身のファイル(Dockerfile等は転送不要) Dockerfile docker-compose*.yml .dockerignore # ドキュメント docs/ README*

3. .dockerignoreの効果を確認する

.dockerignore を配置した後のビルドコンテキスト転送量を確認してみます。

# .dockerignore 設定後 $ docker build . [+] Building 0.9s (2/2) => [internal] load build context => => transferring context: 14.2kB # 1.23GB から 14kB に削減 # コンテキストサイズの変化 # 設定前: 1.23 GB(node_modules + .git が大半) # 設定後: 14.2 kB(ソースコードのみ)

node_modules や .git を除外するだけで転送量が100分の1以下になることは珍しくありません。特にNode.jsプロジェクトでは node_modules が数百MBになることも多く、.dockerignore はプロジェクト開始時に必ず作成する設定です。

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、Dockerfileのビルド最適化からコンテナ本番運用まで体系的に学べるDocker講座を用意しています。
Dockerマスター講座の詳細はこちら >>

BuildKitのキャッシュマウントでパッケージキャッシュを永続化する

1. BuildKitとは何か

Docker 23.0 以降ではデフォルトで BuildKit が有効になっています(Docker 18.09から22.x では DOCKER_BUILDKIT=1 の環境変数指定が必要)。BuildKit は従来のビルダーより高速で、キャッシュマウントという強力な機能を提供します。

# BuildKit の有効確認(Docker 23.0+ ではデフォルトで有効) $ docker buildx version github.com/docker/buildx v0.14.1 081af9c # Docker 22.x 以前で BuildKit を有効化する $ export DOCKER_BUILDKIT=1 $ docker build .

2. apt・pip・npmのキャッシュをマウントする

BuildKit の --mount=type=cache を使うと、パッケージマネージャーのダウンロードキャッシュをビルド間で永続化できます。キャッシュはイメージには含まれないため、イメージサイズを増やさずにビルドを高速化できます。

# Debian/Ubuntu系(apt)のキャッシュマウント FROM ubuntu:24.04 RUN --mount=type=cache,target=/var/cache/apt \ --mount=type=cache,target=/var/lib/apt \ apt-get update && apt-get install -y \ curl \ git \ build-essential

# Python(pip)のキャッシュマウント FROM python:3.12-slim WORKDIR /app COPY requirements.txt . RUN --mount=type=cache,target=/root/.cache/pip \ pip install -r requirements.txt COPY . . CMD ["python", "app.py"]

# Node.js(npm)のキャッシュマウント FROM node:20-slim WORKDIR /app COPY package*.json . RUN --mount=type=cache,target=/root/.npm \ npm ci --prefer-offline COPY . . CMD ["node", "server.js"]

--mount=type=cache,target= で指定したディレクトリは、ビルド間でホスト側に永続化されます。2回目以降は既にダウンロード済みのパッケージを使うため、ネットワークアクセスが大幅に減ります。CI環境でも同様の効果が期待できます(ただしキャッシュボリュームの扱いはCI製品によって異なります)。

注意点として、--mount=type=cache を使う場合は Dockerfile の先頭に # syntax=docker/dockerfile:1 を追加するか、docker buildx build を使う必要があります(通常の docker build でも BuildKit が有効なら動作します)。

トラブルシュート:キャッシュが使われない・古いキャッシュが残る

1. キャッシュを強制的に無効化する(--no-cache)

「キャッシュが古くて正しいビルド結果が得られない」場合や、「イメージを完全にクリーンな状態から作り直したい」場合は --no-cache を指定します。

# すべてのレイヤーを再ビルドする $ docker build --no-cache -t myapp:latest . # 特定のステージからキャッシュを無効化したい場合 # ARG CACHEBUST を使う方法 ARG CACHEBUST=1 RUN curl -sf https://example.com/install.sh | sh # ビルド時に --build-arg CACHEBUST=$(date +%s) を指定するとこの行以降のキャッシュが無効化される

すべての命令が再実行されるため時間はかかりますが、確実にクリーンなイメージが作れます。パッケージの脆弱性対応後の再ビルドや、リリースビルドに使います。

2. ADD命令はURLとtar展開でキャッシュが使われない

ADD 命令にURLを指定した場合、Dockerはリモートファイルの変更を毎回確認するためキャッシュが使われません。

# キャッシュが使われない ADD(URL指定) ADD https://example.com/app.tar.gz /app/ # 毎回ダウンロードが走る # COPY を使う(キャッシュが有効) COPY app.tar.gz /app/ RUN tar -xzf /app/app.tar.gz -C /app/ && rm /app/app.tar.gz

ADD よりも COPY + RUN tar を組み合わせる方がキャッシュの動作が予測しやすく、実務では COPY の使用が推奨されます(ADDのtar自動展開は便利ですが、挙動がわかりにくいため)。

3. ビルドコンテキストが大きすぎる時の診断

ビルド開始直後に transferring context で時間がかかる場合、ビルドコンテキストが大きすぎます。

# ビルドコンテキストのサイズを確認する $ docker build --progress=plain . 2>&1 | grep "transferring context" #2 [internal] load build context #2 transferring context: 852.40MB # 大きすぎる場合は .dockerignore を追加 # 大きいディレクトリを確認する $ du -sh node_modules/ .git/ __pycache__/ 2>/dev/null 412M node_modules/ 38M .git/ 1.2M __pycache__/

.gitnode_modules__pycache__、テストデータ等が原因であることが多いです。これらを .dockerignore に追加するだけで大幅な改善が見込めます。

本記事のまとめ

Dockerのビルドキャッシュと .dockerignore の設計ポイントをまとめます。

やりたいこと 対処方法
依存パッケージの再インストールを防ぐ 依存定義ファイルだけ先にCOPYし、RUNインストール後にソースをCOPY
ビルドコンテキストの転送を速くする .dockerignoreで node_modules・.git・キャッシュ等を除外
apt/pip/npmのダウンロードをキャッシュする BuildKitの --mount=type=cache,target= を使う
完全クリーンビルドを実行する docker build --no-cache
キャッシュが使われない命令を避ける URLを指定した ADD は使わず、COPY に置き換える
・Dockerのキャッシュは命令が変わった行以降すべて無効化される——「変更頻度の低い命令を上」「変更頻度の高い命令を下」が基本原則
・.dockerignore がないと開発環境の不要ファイルが毎回転送される——プロジェクト開始時に必ず作成する
・BuildKitのキャッシュマウントはイメージサイズを増やさずにパッケージキャッシュを永続化できる——CI環境での導入効果が大きい

現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、ビルド最適化からコンテナ本番運用まで実機ハンズオンで学べるDocker講座を用意しています。
Dockerマスター講座の詳細はこちら >>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は1分、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

登録10秒/合わなければ解除3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。