原因の多くは 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でパッケージキャッシュを永続化できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜ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"]
実際のビルド時間の差を確認した例です。
# 最適化前(ソースコードを変更した後の再ビルド) $ 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のみがビルドに使われるわけではなく、指定したディレクトリ全体が転送されるのです。# .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 の例(プロジェクトルートに置く) # バージョン管理 .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(ソースコードのみ)
現場で通用する安全な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 node_modules/ .git/ __pycache__/ 2>/dev/null 412M node_modules/ 38M .git/ 1.2M __pycache__/
.git、node_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 に置き換える |
・.dockerignore がないと開発環境の不要ファイルが毎回転送される——プロジェクト開始時に必ず作成する
・BuildKitのキャッシュマウントはイメージサイズを増やさずにパッケージキャッシュを永続化できる——CI環境での導入効果が大きい
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、ビルド最適化からコンテナ本番運用まで実機ハンズオンで学べるDocker講座を用意しています。
→ Dockerマスター講座の詳細はこちら >>
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
登録10秒/合わなければ解除3秒 / 詳細はこちら
- 次のページへ:Dockerコンテナのリソース制限と監視|--memory・--cpusとdocker statsでホストを守る設計
- 前のページへ:Docker Composeのdepends_onとHEALTHCHECK|サービス起動順序を確実に制御する設計パターン
- この記事の属するカテゴリ:Dockerへ戻る

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