Dockerが遅い原因を「ビルド・実行・I/O」の3層で切り分けて直す(2026年版)
Dockerのビルド・起動・ファイルI/Oが遅い原因を3つの層に分けて切り分け、公式ドキュメント準拠のBuildKitキャッシュ・リソース制限・ボリューム設定で改善する手順をコマンド付きで解説する。
エンジニアのゆとです。
「Dockerが遅い」で検索すると、だいたい2種類の記事に行き着く。1つは2015〜2021年頃に書かれたベンチマーク検証記事(VMとの速度比較とか、ストレージドライバの学術的な検証とか)。もう1つはMacのDocker Desktopがメモリを食う話。どちらも間違ってはいないんだけど、「今まさに手元のビルドが遅い」「本番のコンテナがCPUを食ってる」というときに欲しい実務的なチェックリストとしては情報が古かったり範囲が狭かったりする。
Macのメモリ問題については以前書いた。

この記事はそれとは別に、OS・実行環境を問わず起きる「Dockerが遅い」を3つの層に分けて切り分ける方法を整理する。3層とは、①ビルドが遅い、②コンテナ実行時にCPU/メモリが遅い、③ファイルI/O・ボリュームが遅い、の3つだ。この3つは原因も対処法も全く別物なのに、「Docker 遅い」で一括りに検索されがちで、間違った層に対処法を当てはめて時間を溶かすケースをよく見る。
まず自分の「遅い」がどの層かを特定する
対処に入る前に、次の3つの質問で自分の状況を切り分ける。
| 症状 | 該当する層 | 典型的な原因 |
|---|---|---|
docker build / docker compose build が毎回長い | ①ビルド | レイヤーキャッシュが効いていない、ベースイメージが重い |
| コンテナ起動後、アプリの応答やバッチ処理が遅い | ②実行時リソース | CPU/メモリのデフォルト無制限設定、ホストとの奪い合い |
| ファイル保存・ホットリロード・DBの読み書きが遅い | ③I/O | bind mountのオーバーヘッド、ボリュームドライバの選択ミス |
この記事では①→②→③の順にチェックリストを並べる。全部読む前に、まず自分がどこで詰まっているかをこの表で確認してから該当セクションに飛んでほしい。
①ビルドが遅い場合のチェックリスト
docker buildが毎回フルで走り直すなら、まずここを疑う。
レイヤー順序を「変更頻度の低い順」にする
Dockerのビルドキャッシュは、Dockerfileの命令を上から順に評価し、ある行の内容(またはそれ以前の行の結果)が変わった時点でそれ以降のキャッシュを全部無効化する。よくあるアンチパターンは、ソースコード全体を先にCOPYしてから依存関係をインストールする書き方だ。これだとソースを1行変えるだけで依存関係の再インストールまで走る。
# 悪い例: ソース変更のたびに依存関係も再インストールされる
COPY . .
RUN npm install
# 良い例: package.jsonが変わらない限りnpm installはキャッシュされる
COPY package.json package-lock.json ./
RUN npm install
COPY . .
公式ドキュメントでも、変更頻度の低い命令(依存関係のインストール)を上位に、頻繁に変わる命令(ソースコードのコピー)を下位に配置することが明記されている。
BuildKitのキャッシュマウントでパッケージマネージャーのDLをスキップする
Docker Engine 23以降はBuildKitがデフォルトのビルダーになっている。BuildKitの--mount=type=cacheを使うと、npm/pip/apt/Cargoなどのパッケージマネージャーがダウンロードしたファイルをビルド間で永続化できる。レイヤーキャッシュが切れても、パッケージそのものの再ダウンロードだけは避けられるのが利点だ。
# syntax=docker/dockerfile:1
RUN --mount=type=cache,target=/root/.npm \
npm install
npm/pip以外にもaptのキャッシュマウント(/var/cache/apt)も同じ書き方で使える。CI環境でビルドのたびに依存関係を全部落とし直している場合、ここを見直すだけで数分単位で短縮できることがある。
マルチステージビルドで最終イメージを軽くする
ビルドに必要なツールチェーン(コンパイラ、devDependencies等)をそのまま本番イメージに残すと、イメージサイズが膨らみpull/push・起動のたびに時間を食う。マルチステージビルドで「ビルド用ステージ」と「実行用ステージ」を分離し、実行に必要な成果物だけをCOPY --from=builderで持ってくる。
FROM node:20 AS builder
WORKDIR /app
COPY . .
RUN npm ci && npm run build
FROM node:20-slim
WORKDIR /app
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules ./node_modules
CMD ["node", "dist/index.js"]
ベースイメージをAlpine/slim/distrolessに変える
nodeやpythonの素のイメージは数百MB〜1GB近くあることが多い。-alpineや-slimタグに切り替えるだけでイメージサイズが数分の1になるケースがある。ただしAlpineはmuslベースなので、glibc依存のネイティブモジュールがあると挙動が変わることがある点は要注意。
.dockerignoreを書く
node_modulesや.git、ビルド成果物のディレクトリを.dockerignoreに書いていないと、COPY . .のたびにそれらがビルドコンテキストとしてDockerデーモンに送信される。ビルドコンテキストが大きいほど、docker buildの最初の「送信」フェーズ自体が遅くなる。
②コンテナ実行時が遅い場合のチェックリスト
ビルドは速いのに、動いているコンテナ自体が遅い場合はこちら。
デフォルトでは「無制限」だと誤解している
まず前提の確認から。Dockerコンテナはデフォルトでは、ホストのCPUサイクルへのアクセスが無制限で、メモリも制限なくホストのメモリを使用できる。つまり「何も設定しなければ勝手にリソースが絞られて遅くなる」わけではなく、逆に無制限だからこそ、同じホスト上の他のコンテナやプロセスとリソースを奪い合って全体が遅くなることがある。
「遅いから制限を外す」ではなく、「制限がないことで他のプロセスと喧嘩している」可能性を先に疑う。
--memoryと--cpusで切り分けて計測する
# メモリを512MBに制限して起動
docker run -m 512m myapp
# CPUを1.5コア分に制限して起動
docker run --cpus="1.5" myapp
# 稼働中コンテナのリアルタイムのCPU/メモリ使用量を確認
docker stats
-m/--memoryは最小6MBから指定できる。--memory-swapを設定しない場合、--memoryと同量のスワップが自動的に許可される点は公式ドキュメントに明記されている。メモリ上限に達すると、LinuxカーネルのOOM Killerがプロセスを強制終了する可能性があり、コンテナだけでなくホスト全体が不安定になるリスクがある。--oom-kill-disableを使う場合は必ず-mと併用しないと、ホストメモリを使い果たすまで暴走しうる。
docker statsで「どのコンテナが重いか」を先に特定する
複数コンテナをdocker composeで動かしている場合、体感で「遅い」と思っても実際にリソースを食っているのは別のコンテナということがよくある。docker statsはリアルタイムでCPU%・メモリ使用量・ネットワークI/O・ブロックI/Oをコンテナ単位で表示するので、対処の前に必ず実行して犯人を絞り込む。
docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"
③ファイルI/O・ボリュームが遅い場合のチェックリスト
ホットリロードが遅い、DBの書き込みが遅い、大量のファイル読み書きを伴う処理が遅い場合はこちら。この層はホストOSによって事情が大きく変わる。
LinuxホストならほぼI/Oオーバーヘッドはない
Linux上でDockerを動かす場合、コンテナはホストのカーネルを直接共有するため、bind mountもnamed volumeもネイティブに近い速度が出る。Linux本番環境で「I/Oが遅い」場合は、マウントの種類よりもストレージドライバ(overlay2が標準)やディスク自体のI/O性能を疑う方が筋が良い。
Mac/Windowsは「仮想化層を経由する」構造がボトルネックになりやすい
macOS・WindowsはLinuxカーネルを持たないため、Docker DesktopはLinux VMを介してコンテナを実行する。ホストのファイルシステムをコンテナにbind mountする場合、macOS↔VM間のファイル同期層(virtiofs等)を経由するため、大量の小さいファイルへの読み書き(node_modulesの展開やホットリロードの監視)で顕著に遅くなる。
対処の方向性は主に3つ。
- named volumeに逃がす:
node_modulesのようにホスト側で直接編集しない大量ファイルは、bind mountではなくnamed volumeに置くとVM内で完結するため速くなる - 同期対象を絞る:
.dockerignoreと同様に、マウントするディレクトリ自体をソースコードのみに絞り、依存関係ディレクトリは除外する - 軽量な仮想化基盤に乗り換える: Docker Desktop自体のVM実装が重い場合、OrbStackやColimaへの乗り換えで改善するケースがある。この選択肢を詳しく比較した記事は上のPostCardLinkを参照してほしい
継続的に監視する
一度直しても、依存関係の追加やコード量の増加で再び遅くなることはよくある。docker statsは手動確認用だが、継続監視には次のような選択肢がある。
- cAdvisor: Googleが公開しているコンテナリソース監視ツール。Prometheus/Grafanaと組み合わせて時系列で可視化できる
- Docker Desktop本体のリソース管理タブ: Mac/Windows版のDocker Desktopには、コンテナごとのCPU/メモリ使用量を確認できるUIが用意されている
よくある質問
DockerとPodmanはパフォーマンスにどれくらい差がある?
Podmanはデーモンレスでrootless実行が可能な設計のため、常駐プロセス(dockerd)を持たない分だけオーバーヘッドが理論上小さい場面がある。ただし実行するコンテナ自体(Linuxカーネルのnamespace/cgroupsを使う点)は同じ仕組みなので、通常のアプリケーション実行速度そのものに大差はない。差が出やすいのは、多数のコンテナを同時に起動・停止するような管理系の操作や、rootlessモードでのネットワーク性能まわりだ。乗り換えを検討するなら、まず自分のワークロードで実際に計測することを勧める。
docker system pruneで速くなる?
ディスク容量の圧迫がI/O性能に影響しているケースでは効果がある。ただしCPU/メモリのボトルネックには直接効かないので、まずdocker statsで犯人を特定してから実行するかどうかを判断した方がいい。ディスク容量まわりの詳しい対処は別記事にまとめてある。

まとめ
「Dockerが遅い」を1つの問題として捉えると対処が発散する。①ビルド、②実行時リソース、③I/O・ボリュームの3層に分けて、まずdocker statsとdocker buildの所要時間でどの層が遅いのかを特定してから、該当するチェックリストに沿って潰していくのが一番早い。Macで開発していて②③の対処だけでは満足できない場合は、冒頭で紹介したOrbStack/Colima移行の記事も合わせて読んでほしい。