原因の多くはDockerfileの命令順序の誤りと .dockerignore ファイルの未設定にあります。Dockerのビルドキャッシュを正しく活用すると、変更のないレイヤーは再利用されるため、5分かかっていたビルドが30秒以下になることも珍しくありません。また .dockerignore を追加するだけで、1GB超のビルドコンテキストが数十KBまで削減されたケースは現場では珍しくなく、機密ファイルのイメージへの混入も同時に防げます。
この記事では、Dockerのビルドキャッシュの仕組みから命令順序の最適化、.dockerignoreの設計(言語別パターン集・セキュリティリスクの防ぎ方)、BuildKitのキャッシュマウント、そして
docker historyを使った肥大レイヤーの診断まで、実務で即使えるノウハウを順番に解説します。RHEL 9.4 / Ubuntu 24.04 LTS + Docker 26.x で動作確認済みです。この記事のポイント
・Dockerのキャッシュは命令が変わった行以降すべて無効化される
・変更頻度の低い命令(apt install)を上に、ソースのCOPYを最後に書く
・.dockerignoreでビルドコンテキストを削減し、転送時間と機密情報漏洩を同時に防ぐ
・BuildKitの--mount=type=cacheでパッケージキャッシュを永続化できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜDockerのビルドが遅くなるのか
Dockerのビルドは、Dockerfile の各命令を1行ずつ実行してレイヤーを積み重ねる仕組みです。各命令の実行結果はレイヤーとしてキャッシュされ、次のビルド時に同じ命令が同じ状態で実行されれば、実際の処理をスキップしてキャッシュから結果を取り出します。各レイヤーは読み取り専用でイミュータブル(変更不可)です。コンテナを起動するとき、読み取り専用レイヤーの上に薄い書き込みレイヤーが1枚追加されます。コンテナを削除するとこの書き込みレイヤーも消えますが、イメージのレイヤーはキャッシュとして残り続けます。
Dockerイメージのレイヤー構造を概念的に示すと次のようになります。
# 上位ほどコンテナに近い(後から重ねたレイヤー) [ 書き込みレイヤー ] ← コンテナ起動時に追加(コンテナ固有・削除で消える) [ COPYレイヤー ] ← イメージの読み取り専用レイヤー [ RUNレイヤー ] ← イメージの読み取り専用レイヤー [ FROM(ベース) ] ← 読み取り専用(python:3.12-slim などのベースイメージ)
また、
docker build . を実行した瞬間に発生する「ビルドコンテキストの転送」も遅さの大きな原因です。BuildKit が有効な環境(Docker Engine 23以降ではデフォルト)では、転送量を次のように確認できます。$ docker build --progress=plain -t myapp:test . 2>&1 | head -6 #0 building with "default" instance using docker driver #1 [internal] load build context #1 transferring context: 247.3MB 9.1s done #1 DONE 9.4s
たとえば、次のような 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"]
実際のビルド時間の差を確認した例です。
# 最適化前(ソースコードを変更した後の再ビルド) $ 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 update と apt-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 clean と rm -rf /var/lib/apt/lists/* は同一レイヤー内に書くことでイメージサイズを削減できます。別の RUN に分けると、前のレイヤーにキャッシュファイルが残ったままイメージサイズに計上されます。.dockerignoreでビルドコンテキストを最小化する
1. ビルドコンテキストとは何か
docker build . を実行すると、カレントディレクトリの内容がビルドコンテキストとしてDockerデーモンに送信されます。DockerfileのCOPYやADDで実際に使われるファイルだけでなく、指定ディレクトリ配下のすべてのファイルが転送対象になる点が重要です。# .dockerignore がない状態でのビルド $ docker build . [+] Building 18.4s (2/2) => [internal] load build context => => transferring context: 1.23GB # .git や node_modules が丸ごと転送されている
2. .dockerignoreの書式と除外パターン
プロジェクトルートに.dockerignore ファイルを作成します。書式は .gitignore と同じで、パターンを1行ずつ記述します。# .dockerignore の基本構造(コメントは # で始める) node_modules # プロジェクトルート直下を除外 **/__pycache__ # 全階層の __pycache__ を除外(** グロブ) *.log # 拡張子一致(サブディレクトリは含まない) .git .env
# tests/ ディレクトリ全体を除外するが fixtures/ だけはコンテキストに残す tests/ !tests/fixtures/
# NG例:否定パターンが除外パターンより先だと効果がない !src/config.toml # こちらが先に評価され… src/ # このパターンで後から除外されるので結局除外される # OK例:除外パターンを先に書いてから否定パターンで戻す src/ # まずフォルダ全体を除外 !src/config.toml # 次に特定ファイルを戻す ← こちらが最終評価なので有効
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(ソースコードのみ)
du -sh で調べられます。# ビルドコンテキストのルートで上位を確認する $ du -sh * .* 2>/dev/null | sort -rh | head -10 421M node_modules 6.4M .git 1.2M dist 348K src 12K .env
# 一時的なDockerfileを渡してコンテキストサイズだけ確認する(ドライラン) $ echo "FROM scratch" | docker build --progress=plain --no-cache -f- . 2>&1 | grep "transferring context" #1 transferring context: 14.2kB 0.0s done
4. 言語別の実践.dockerignoreパターン集
プロジェクトの言語・フレームワークに合わせたパターンを紹介します。プロジェクト開始時にそのまま使えるテンプレートとして活用してください。Node.js / JavaScript プロジェクト
# Node.js プロジェクト用 .dockerignore # 依存パッケージ(コンテナ内で再インストールするため不要) node_modules npm-debug.log* yarn-debug.log* yarn-error.log* # ビルド成果物(コンテナ内でビルドするため不要) dist build .next .nuxt # 開発・テスト用 .env .env.local .env.*.local coverage .nyc_output # バージョン管理 .git .gitignore # IDE・エディタ設定 .vscode .idea *.swp
# Python プロジェクト用 .dockerignore # バイトコード・キャッシュ __pycache__ *.pyc *.pyo *.pyd # 仮想環境 .venv venv env .env # テスト・カバレッジ .pytest_cache .coverage htmlcov .tox # ビルド成果物 dist build *.egg-info # バージョン管理 .git .gitignore # IDE設定 .vscode .idea *.swp
# Java プロジェクト用 .dockerignore # Mavenローカルリポジトリ(コンテナ内でダウンロードするため不要) .m2 # ビルド成果物 target build # Gradle .gradle gradle/wrapper/gradle-wrapper.jar # IDE設定 .idea *.iml .classpath .project .settings # 環境設定 .env *.properties.local # バージョン管理 .git .gitignore
5. .dockerignoreで防ぐセキュリティリスク
.dockerignore はビルド高速化だけでなく、機密情報の漏洩防止にも直結します。COPY . . を使っている場合は特に注意が必要です。.envファイルとAPIキーのイメージへの混入
.env ファイルにはデータベースのパスワードや外部APIのシークレットキーが記述されていることが多く、Dockerfileに誤って
COPY . . と書いていると .env ごとイメージに取り込まれます。一度イメージに含まれると、docker history やレイヤーの展開でそのまま読み取れます。# イメージ内の .env を docker run で確認できてしまう例 $ docker run --rm myapp cat /app/.env DB_PASSWORD=supersecret123 AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY
.env を除外することが第一の対策です。加えてDockerfileでは COPY . . より COPY src/ /app/src/ のように必要なパスだけを明示する習慣をつけると、.dockerignore の記述漏れによる事故を二重に防げます。.gitディレクトリのコミット履歴と設定の流出
.git ディレクトリには、
.git/config にリポジトリのリモートURL(認証トークン付きURL)が含まれることがあります。また過去にコミットした機密情報が履歴として残っている場合、そのコミットごとイメージに入ることになります。.dockerignore に .git を加えることで、このリスクを完全に排除できます。SSHキー・証明書の誤包含
開発者の .ssh ディレクトリや秘密鍵ファイルをプロジェクトルートに置いたまま
COPY . . を実行すると、秘密鍵がイメージに混入します。ビルド時に一時的にSSHキーが必要な場合は、コンテキストに含めるのではなくBuiltKitの RUN --mount=type=ssh(BuildKit secret mount)を使うのが正しい設計です。# セキュリティ観点で必ず除外すべきパターン .env .env.* .envrc .ssh *.pem *.key *.p12 *.pfx id_rsa id_ed25519 id_ecdsa .git # クラウド認証情報 .aws/ .kube/ # OS固有の一時ファイル .DS_Store
docker history で確認できます。# イメージのレイヤー履歴を確認する $ docker history --no-trunc myapp:latest IMAGE CREATED CREATED BY SIZE sha256:a3f... 3 minutes ago COPY . . # buildkit 249MB sha256:b7c... 3 minutes ago RUN /bin/sh -c npm install # buildkit 78MB
現場で通用する安全な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
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 * .* 2>/dev/null | sort -rh | head -10 412M node_modules/ 38M .git/ 1.2M __pycache__/
.git、node_modules、__pycache__、テストデータ等が原因であることが多いです。これらを .dockerignore に追加するだけで大幅な改善が見込めます。4. COPYするファイルが自動的に変化している
ビルド日時をコメントに含むファイルや、ビルドのたびに自動生成されるファイルをCOPYしていると、内容が毎回変わってキャッシュが無効になります。.dockerignoreで除外するか、ファイルの生成タイミングを見直してください。# NG例:ビルドのたびにタイムスタンプが変わるファイルをCOPY # build-info.txt(内容例): "Built at 2026-08-17 12:00:00" COPY build-info.txt /app/ # 対処: .dockerignore で除外し、RUN で生成する方式に変更 # .dockerignore に追加: # build-info.txt # Dockerfile 内で生成に変更: RUN echo "Built at $(date)" > /app/build-info.txt
.dockerignore で除外するのが確実です。5. latestタグのベースイメージが更新されキャッシュが無効になる
FROM python:latest のようにlatestタグを使っていると、上流イメージが更新されるたびにFROMのダイジェストが変わり、以降のすべてのキャッシュが無効になります。バージョンタグを固定することで回避できます。# NG: latestは予期しないタイミングでダイジェストが変わる FROM python:latest # OK: パッチバージョンまで固定して意図しないキャッシュ無効を防ぐ FROM python:3.12.3-slim
6. docker historyで肥大レイヤーを特定する
ビルド自体は速くなったのにイメージサイズが大きい、という場合はdocker history でどのレイヤーが重いかを確認します。# サイズの大きいレイヤーを上位5件表示 $ docker history --format "{{.Size}} {{.CreatedBy}}" myapp:latest | sort -rh | head -5 # 実行結果の例 492MB /bin/sh -c apt-get update && apt-get install -y build-essential gcc g++ cmake ... 134MB /bin/sh -c pip install torch==2.1.0 27MB /bin/sh -c #(nop) ADD file:d37ff24540ea66a19... 8.2MB /bin/sh -c pip install --no-cache-dir -r requirements.txt
--no-trunc オプションを付けると RUN 命令の全文が表示されるため、どのコマンドが原因かを正確に特定できます。# 命令全文を省略なしで表示する $ docker history --no-trunc myapp:latest
7. .dockerignoreが効かない時の確認手順
.dockerignore を設置しても転送量が減らない場合、次の点を順に確認してください。配置場所を確認する
docker build -f docker/Dockerfile . のようにDockerfileのパスを -f で指定している場合でも、.dockerignore はビルドコンテキスト(最後の引数 .)のルートに置く必要があります。# OK: ビルドコンテキスト(カレントディレクトリ)のルートに置く $ ls -la .dockerignore ← ここに置く docker/ Dockerfile src/ $ docker build -f docker/Dockerfile .
Windows環境で .dockerignore を作成すると、改行コードがCRLF(\r\n)になることがあります。LinuxのDockerデーモンがパターンを正常に解析できなくなる場合があるため、LFに変換してください。
# 改行コードを確認する $ file .dockerignore .dockerignore: ASCII text, with CRLF line terminators # LFに変換する(dos2unixが使える場合) $ dos2unix .dockerignore # dos2unixがない場合 $ sed -i 's/\r//' .dockerignore # 変換後の確認 $ file .dockerignore .dockerignore: ASCII text
.dockerignore はコンテキスト内のパスをルートからの相対パスで記述します。サブディレクトリ内のファイルを除外したい場合は、ルートからのパスまたは
** グロブを使います。# NG: ルート直下の __pycache__ しか除外できない __pycache__ # OK: 全階層の __pycache__ を除外する **/__pycache__
・ディレクトリのみ除外したい場合:末尾に
/ を付ける(例: dist/)・否定パターンが効かない:評価順序を確認する——除外パターンが否定パターンの前に来ているか
・Dockerfileや.dockerignore自体を除外しない:ビルドに必要なDockerfileが消えてエラーになる
本記事のまとめ
Dockerのビルドキャッシュと .dockerignore の設計ポイントをまとめます。| やりたいこと | 対処方法 |
|---|---|
| 依存パッケージの再インストールを防ぐ | 依存定義ファイルだけ先にCOPYし、RUNインストール後にソースをCOPY |
| ビルドコンテキストの転送を速くする | .dockerignoreで node_modules・.git・キャッシュ等を除外 |
| 機密ファイルのイメージへの混入を防ぐ | .dockerignoreで .env・*.pem・.ssh・.git・.aws・.kube を必ず除外する |
| apt/pip/npmのダウンロードをキャッシュする | BuildKitの --mount=type=cache,target= を使う |
| 完全クリーンビルドを実行する | docker build --no-cache |
| キャッシュが使われない命令を避ける | URLを指定した ADD は使わず、COPY に置き換える |
| ベースイメージの意図しない更新を防ぐ | FROM python:latest ではなくパッチバージョンまで固定する |
| 肥大レイヤーの原因を特定する | docker history --format "{{.Size}}\t{{.CreatedBy}}" イメージ名 | sort -rh |
| コンテキストに含まれるファイルを事前確認する | echo "FROM scratch" | docker build --progress=plain --no-cache -f- . |
| .dockerignoreが効かない時 | ビルドコンテキストのルートに置かれているか・改行コードがLFか・**グロブを使っているか確認する |
・.dockerignore がないと開発環境の不要ファイルが毎回転送される——プロジェクト開始時に必ず作成し、.env・.git・node_modules の3つは最優先で除外する
・.env や .git をコンテキストに含めると機密情報がイメージに混入するリスクがある——.aws/.kube 等クラウド認証情報も忘れず除外する
・BuildKitのキャッシュマウントはイメージサイズを増やさずにパッケージキャッシュを永続化できる——CI環境での導入効果が大きい
・
docker history でレイヤーごとのサイズを可視化し、マルチステージビルドで肥大レイヤーを除去する
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、ビルド最適化からコンテナ本番運用まで実機ハンズオンで学べるDocker講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 次のページへ:Dockerコンテナのリソース制限と監視|--memory・--cpusとdocker statsでホストを守る設計
- 前のページへ:Docker Composeのdepends_onとHEALTHCHECK|サービス起動順序を確実に制御する設計パターン
- この記事の属するカテゴリ:Dockerへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます