ビルドを高速化しイメージを小さくするためのDockerベストプラクティス5選
この記事は、Dockerfile の構築プロセスを最適化し、イメージサイズを大幅に削減してビルド時間を短縮するための具体的なベストプラクティスと技術的アプローチを解説している。
キーポイント
ベースイメージの選択戦略
標準的なフルイメージ(例:python:3.11)よりも、不要なツールを削除した slim イメージや、musl ベースでさらに軽量な alpine イメージを選択することで、最小限のサイズを実現する。
ビルドキャッシュの最大化
依存関係ファイル(requirements.txt など)の変更頻度が低いコードを先にコピーし、インストールコマンドを実行することで、変更があった際にもキャッシュを活用して再構築時間を秒単位にする。
マルチステージビルドの活用
コンパイルや依存関係のインストールを行う「ビルドステージ」と、最終的な実行環境を提供する「ランタイムステージ」を分離し、不要な開発ツールや中間ファイルを最終イメージから排除する。
ビルドコンテキストの最適化
.dockerignore ファイルを適切に設定して、不要なファイル(git 履歴、ログ、テストデータなど)がコンテナ内部にコピーされるのを防ぎ、送信データを最小化する。
ベースイメージの選択基準
Python プロジェクトには安全性の高い slim イメージをデフォルトとし、依存関係の互換性が確認できている場合のみサイズ縮小のために Alpine を使用すべきです。
インストールレイヤー内でのクリーンアップ
apt-get などのパッケージ管理ツールによるキャッシュファイルを削除する際、その削除コマンドをインストールコマンドと同じ RUN 指令内で実行しないと、中間層にデータが残ってイメージサイズが大きくなります。
パッケージ管理後の一時ファイル削除
apt-get install の実行後、同じ RUN コマンド内で rm -rf /var/lib/apt/lists/* を実行し、キャッシュファイルを削除する習慣をつける。
重要な引用
A few focused changes can shrink your image by 60 — 80% and turn most rebuilds from minutes into seconds.
The official python:3.11 image is a full Debian-based image loaded with compilers, utilities, and packages that most applications never use.
if a layer changes, every layer after it is invalidated and rebuilt from scratch
copy the things that change least, first
The build tools never make it into the image you ship.
To actually remove them, the cleanup must happen in the same RUN instruction as the install.
影響分析・編集コメントを表示
影響分析
この記事は、クラウドコストの削減やデプロイメント時間の短縮に直結する実務的な知見を提供しており、DevOps や SRE の現場において即座に適用可能な価値があります。特に大規模なマイクロサービス環境や CI/CD パイプラインを持つ組織にとっては、インフラ効率化の重要な指針となります。
編集コメント
AI モデルのトレーニングや推論環境を Docker で構築する際、このベストプラクティスを実践することで、リソース効率とデプロイ速度が劇的に向上します。特に大規模なモデルを扱う場合、イメージサイズの削減はストレージコストと転送時間の面で極めて重要です。
著者による画像
# はじめに
Dockerfile を作成し、イメージをビルドして、すべてが正常に動作することを確認しました。しかし、その後で気づきます。イメージのサイズが 1 ギガバイトを超えており、最小限の変更でも再ビルドに数分もかかり、プッシュやプルが非常に遅いことに。
これは珍しいことではありません。ベースイメージの選択、ビルドコンテキスト、キャッシュについて考えずに Dockerfile を記述した場合、これらはデフォルトの結果です。それを修正するために完全な見直しをする必要はありません。いくつかの焦点を絞った変更で、イメージサイズを 60〜80% 縮小し、ほとんどの再ビルドを数分から数秒に短縮できます。
この記事では、Docker イメージを小さく、高速で効率的にするための 5 つの実践的なテクニックを見ていきます。
# 前提条件
これに従うには、以下が必要です:
- Docker がインストールされていること
- Dockerfile および docker build コマンドに関する基本的な知識
- requirements.txt ファイルを持つ Python プロジェクト(例では Python を使用していますが、この原則はあらゆる言語に適用されます)
# スリムまたは Alpine ベースイメージの選択
すべての Dockerfile は FROM 命令で始まり、ベースイメージを選択します。そのベースイメージはアプリケーションの基盤であり、独自のコードを一行も追加する前の最小限のイメージサイズになります。
例えば、公式の python:3.11 イメージは、コンパイラ、ユーティリティ、そしてほとんどのアプリケーションが使用しないパッケージを多数含む、フル機能の Debian ベースのイメージです。
完全なイメージ — 全機能を含むもの
FROM python:3.11
スリムイメージ — 最小限のDebianベース
FROM python:3.11-slim
Alpineイメージ — さらに小さく、muslベースのLinux
FROM python:3.11-alpine
それぞれからイメージをビルドし、サイズを確認してみましょう。
docker images | grep python
1行のDockerfileを変更するだけで、数百メガバイトもの差が生じます。では、どれを使うべきでしょうか?
- スリム版は、ほとんどのPythonプロジェクトにとって安全なデフォルトです。不要なツールを削除しますが、多くのPythonパッケージが正しくインストールするために必要なCライブラリは保持されます。
- Alpine版はさらに小さいですが、glibcの代わりにmuslという異なるCライブラリを使用しており、特定のPythonパッケージとの互換性問題を引き起こす可能性があります。そのため、イメージサイズの削減で得られる時間よりも、失敗したpipインストールのデバッグに費やす時間の方が多くなる可能性があります。
目安: まず python:3.1x-slim から始めます。依存関係が互換性があり、さらにサイズを削減する必要があることが確実な場合にのみ、Alpine版に切り替えてください。
レイヤーの順序を最適化してキャッシュを最大化する
Dockerはイメージをレイヤーごとに、1つの命令ずつビルドします。一度ビルドされたレイヤーはDockerによってキャッシュされます。次のビルド時、そのレイヤーに影響を与える変更がない場合、Dockerはキャッシュされたバージョンを再利用し、再ビルドをスキップします。
ただし注意すべき点は、1つのレイヤーが変更されると、その後のすべてのレイターが無効化され、最初から再ビルドされるということです。
これは依存関係のインストールにおいて非常に重要です。よくある間違いは以下の通りです。
悪いレイヤー順序 — コード変更ごとに依存関係が再インストールされる
FROM python:3.11-slim
WORKDIR /app
COPY . . # コードなどすべてをコピーする
RUN pip install -r requirements.txt # コピー後に実行されるため、ファイルが変更されるたびに再実行される
スクリプトの1行を変更するたびに、Dockerは COPY . . のレイヤーを無効化し、依存関係をすべて最初から再インストールします。requirements.txt が重いプロジェクトでは、ビルドのたびに数分が無駄になります。
解決策はシンプルです。変更頻度が最も低いものを先にコピーすることです。
良好なレイヤー順序 — requirements.txt が変更されない限り依存関係はキャッシュされる
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt . # まず requirements のみをコピーする
RUN pip install --no-cache-dir -r requirements.txt # 依存関係をインストール — このレイヤーはキャッシュされる
COPY . . # コードを最後にコピーする — コード変更時にこのレイヤーのみが再実行される
CMD ["python", "app.py"]
これで app.py を変更しても、Docker はキャッシュされた pip レイヤーを再利用し、最後の COPY . . のみ再実行します。
目安: COPY および RUN 命令を、変更頻度が低い順から高い順に並べます。依存関係は常にコードより先に配置します。
# マルチステージビルドの活用
**
一部のツールはビルド時だけに必要です — コンパイラ、テストランナー、ビルド依存関係など — しかし、それらが最終イメージに含まれてしまい、実行中のアプリケーションが触れることのないものでイメージが肥大化してしまいます。
マルチステージビルドはこれを解決します。必要なものをすべてビルドまたはインストールするために1つのステージを使用し、完成した出力のみをクリーンで最小限の最終イメージにコピーします。ビルドツールは、出荷するイメージには含まれません。
ここでは、依存関係をインストールしながら最終イメージを軽量に保つPythonの例を示します:
シングルステージ — ビルドツールが最終イメージに含まれる
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y gcc build-essential
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]
マルチステージビルドを使用する場合:
マルチステージ — ビルドツールはビルダーステージにのみ残る
ステージ1: builder — 依存関係をインストール
FROM python:3.11-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y gcc build-essential
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
ステージ2: runtime — 必要なものだけを含むクリーンなイメージ
FROM python:3.11-slim
WORKDIR /app
ビルダーステージからインストールされたパッケージのみをコピー
COPY --from=builder /install /usr/local
COPY . .
CMD ["python", "app.py"]
gcc や build-essential といったツールは、一部の Python パッケージをコンパイルするために必要ですが、最終的なイメージからは削除されています。コンパイル済みのパッケージがコピーされたため、アプリケーションは正常に動作します。ビルドツール自体はビルダーステージに残され、Docker によって破棄されます。このパターンは、数百メガバイトにも及ぶコンパイラや node modules が配送用イメージから完全に除外される Go や Node.js のプロジェクトにおいて、さらに大きな影響を持ちます。
インストールレイヤー内でのクリーンアップ
apt-get を使用してシステムパッケージをインストールする場合、パッケージマネージャーはランタイム時に必要としないパッケージリストやキャッシュファイルをダウンロードします。これらを別の RUN 命令で削除した場合、それらは中間レイヤーに存在し続け、Docker のレイヤーシステムにより最終的なイメージサイズに影響を与え続けます。
実際に削除するには、クリーンアップをインストールと同じ RUN 命令内で行う必要があります。
別のレイヤーでのクリーンアップ — キャッシュファイルがイメージを肥大化させる
FROM python:3.11-slim
RUN apt-get update && apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/* # すでに上のレイヤーでコミット済み
同じレイヤーでのクリーンアップ — イメージに何もコミットされない
FROM python:3.11-slim
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*
この論理は、他のパッケージマネージャーや一時ファイルにも適用されます。
目安: どの apt-get install の後にも、同じ RUN コマンド内で && rm -rf /var/lib/apt/lists/* を続けるべきです。これを習慣にしてください。
.dockerignore ファイルの実装
docker build を実行すると、Docker はビルドディレクトリ内のすべてのファイルをビルドコンテキストとして Docker デーモンに送信します。これは Dockerfile 内の命令が実行される前に行われ、イメージに含まれることをほぼ確実に望まないファイルが含まれていることがよくあります。
.dockerignore ファイルがない場合、プロジェクトフォルダ全体が送信されます。.git の履歴、仮想環境、ローカルのデータファイル、テストフィクスチャ、エディタの構成ファイルなどがそれにあたります。これによりすべてのビルドが遅くなり、機密性の高いファイルがイメージにコピーされるリスクが生じます。
.dockerignore ファイルは .gitignore とまったく同じように機能し、ビルドコンテキストから除外するファイルやフォルダを Docker に指示します。
以下は、典型的な Python データプロジェクトの .dockerignore のサンプル(ただし省略あり)です。
Python
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.egg-info/
仮想環境
.venv/
venv/
env/
データファイル(大きなデータセットをイメージに焼き込まない)
data/
*.csv
*.parquet
*.xlsx
Jupyter
.ipynb_checkpoints/
*.ipynb
...
テスト
tests/
pytest_cache/
.coverage
...
機密情報 — これらがイメージに含まれないようにする
.env
*.pem
*.key
これにより、ビルドが開始される前に Docker デーモンに送信されるデータの量が大幅に削減されます。parquet ファイルや生の CSV がプロジェクトフォルダにあるような大規模なデータプロジェクトでは、これは 5 つのプラクティスの中で最大の成果となる可能性があります。
セキュリティの観点からも注目に値する点があります。プロジェクトフォルダに API キーやデータベース認証情報を含む .env ファイルが含まれている場合、.dockerignore を忘れると、これらの秘密情報がイメージに組み込まれてしまう可能性があります。特に、広範な COPY . . 命令を使用している場合です。
基本原則: .env ファイルや認証情報ファイルに加え、イメージに組み込む必要のないデータファイルもすべて .dockerignore に追加してください。また、機密データには Docker secrets を使用してください。
# まとめ
**
これらの手法のいずれも高度な Docker の知識は必要なく、どちらかといえば「習慣」に近いものです。これらを一貫して適用すれば、イメージは小さくなり、ビルドは高速化し、デプロイもクリーンになります。
実践 | 解決する問題
---|---
Slim/Alpine ベースイメージ | 必須の OS パッケージのみで開始することで、イメージの小型化を保証します。
レイヤー順序 | コード変更ごとに依存関係の再インストールを回避します。
マルチステージビルド | 最終イメージからビルドツールを除外します。
同一レイヤーでのクリーンアップ | apt キャッシュが中間レイヤーを肥大化するのを防ぎます。
.dockerignore | ビルドコンテキストを削減し、秘密情報をイメージから隔離します。
Happy coding!
Bala Priya C はインド出身の開発者かつテクニカルライターです。数学、プログラミング、データサイエンス、コンテンツ作成の交差点で作業することを好んでいます。彼女の興味分野および専門知識には DevOps、データサイエンス、自然言語処理(Natural Language Processing)が含まれます。読書、執筆、コーディング、そしてコーヒーを楽しむことを得意としています。現在、チュートリアル、ハウツーガイド、意見記事などの作成を通じて、開発者コミュニティに知識の習得と共有を行っています。また、Bala は魅力的なリソースの概要やコーディングチュートリアルも作成しています。
原文を表示

**
Image by Author
# Introduction
You've written your Dockerfile, built your image, and everything works. But then you notice the image is over a gigabyte, rebuilds take minutes for even the smallest change, and every push or pull feels painfully slow.
This isn’t unusual. These are the default outcomes if you write Dockerfiles without thinking about base image choice, build context, and caching. You don’t need a complete overhaul to fix it. A few focused changes can shrink your image by 60 — 80% and turn most rebuilds from minutes into seconds.
In this article, we’ll walk through five practical techniques so you can learn how to make your Docker images smaller, faster, and more efficient.
# Prerequisites
To follow along, you'll need:
- Docker installed
- Basic familiarity with Dockerfiles and the docker build command
- A Python project with a requirements.txt file (the examples use Python, but the principles apply to any language)
# Selecting Slim or Alpine Base Images
Every Dockerfile starts with a FROM instruction that picks a base image. That base image is the foundation your app sits on, and its size becomes your minimum image size before you've added a single line of your own code.
For example, the official python:3.11 image is a full Debian-based image loaded with compilers, utilities, and packages that most applications never use.
# Full image — everything included
FROM python:3.11
# Slim image — minimal Debian base
FROM python:3.11-slim
# Alpine image — even smaller, musl-based Linux
FROM python:3.11-alpineNow build an image from each and check the sizes:
docker images | grep pythonYou’ll see several hundred megabytes of difference just from changing one line in your Dockerfile. So which should you use?
- slim is the safer default for most Python projects. It strips out unnecessary tools but keeps the C libraries that many Python packages need to install correctly.
- alpine is even smaller, but it uses a different C library — musl instead of glibc — that can cause compatibility issues with certain Python packages. So you may spend more time debugging failed pip installs than you save on image size.
Rule of thumb: start with python:3.1x-slim**. Switch to alpine only if you're certain your dependencies are compatible and you need the extra size reduction.
// Ordering Layers to Maximize Cache
Docker builds images layer by layer, one instruction at a time. Once a layer is built, Docker caches it. On the next build, if nothing has changed that would affect a layer, Docker reuses the cached version and skips rebuilding it.
The catch: if a layer changes, every layer after it is invalidated and rebuilt from scratch.
This matters a lot for dependency installation. Here's a common mistake:
# Bad layer order — dependencies reinstall on every code change
FROM python:3.11-slim
WORKDIR /app
COPY . . # copies everything, including your code
RUN pip install -r requirements.txt # runs AFTER the copy, so it reruns whenever any file changesEvery time you change a single line in your script, Docker invalidates the COPY . . layer, and then reinstalls all your dependencies from scratch. On a project with a heavy requirements.txt, that's minutes wasted per rebuild.
The fix is simple: copy the things that change least, first.
# Good layer order — dependencies cached unless requirements.txt changes
FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt . # copy only requirements first
RUN pip install --no-cache-dir -r requirements.txt # install deps — this layer is cached
COPY . . # copy your code last — only this layer reruns on code changes
CMD ["python", "app.py"]Now when you change app.py, Docker reuses the cached pip layer and only re-runs the final COPY . ..
Rule of thumb: order your COPY and RUN instructions from least-frequently-changed to most-frequently-changed. Dependencies before code, always.
# Utilizing Multi-Stage Builds
**
Some tools are only needed at build time — compilers, test runners, build dependencies — but they end up in your final image anyway, bloating it with things the running application never touches.
Multi-stage builds solve this. You use one stage to build or install everything you need, then copy only the finished output into a clean, minimal final image. The build tools never make it into the image you ship.
Here's a Python example where we want to install dependencies but keep the final image lean:
# Single-stage — build tools end up in the final image
FROM python:3.11-slim
WORKDIR /app
RUN apt-get update && apt-get install -y gcc build-essential
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "app.py"]Now with a multi-stage build:
# Multi-stage — build tools stay in the builder stage only
# Stage 1: builder — install dependencies
FROM python:3.11-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y gcc build-essential
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# Stage 2: runtime — clean image with only what's needed
FROM python:3.11-slim
WORKDIR /app
# Copy only the installed packages from the builder stage
COPY --from=builder /install /usr/local
COPY . .
CMD ["python", "app.py"]The gcc and build-essential tools — needed to compile some Python packages — are gone from the final image. The app still works because the compiled packages were copied over. The build tools themselves were left behind in the builder stage, which Docker discards. This pattern is even more impactful in Go or Node.js projects, where a compiler or node modules that are hundreds of megabytes can be completely excluded from the shipped image.
# Cleaning Up Within the Installation Layer
When you install system packages with apt-get, the package manager downloads package lists and caches files that you don't need at runtime. If you delete them in a separate RUN instruction, they still exist in the intermediate layer, and Docker's layer system means they still contribute to the final image size.
To actually remove them, the cleanup must happen in the same RUN instruction as the install.
# Cleanup in a separate layer — cached files still bloat the image
FROM python:3.11-slim
RUN apt-get update && apt-get install -y curl
RUN rm -rf /var/lib/apt/lists/* # already committed in the layer above
# Cleanup in the same layer — nothing is committed to the image
FROM python:3.11-slim
RUN apt-get update && apt-get install -y curl \
&& rm -rf /var/lib/apt/lists/*The same logic applies to other package managers and temporary files.
Rule of thumb**: any apt-get install should be followed by && rm -rf /var/lib/apt/lists/* in the same RUN command. Make it a habit.
# Implementing .dockerignore Files
**
When you run docker build, Docker sends everything in the build directory to the Docker daemon as the build context. This happens before any instructions in your Dockerfile run, and it often includes files you almost certainly don't want in your image.
Without a .dockerignore file, you're sending your entire project folder: .git history, virtual environments, local data files, test fixtures, editor configs, and more. This slows down every build and risks copying sensitive files into your image.
A .dockerignore file works exactly like .gitignore; it tells Docker which files and folders to exclude from the build context.
Here's a sample, albeit truncated, .dockerignore for a typical Python data project:
# Python
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
*.egg-info/
# Virtual environments
.venv/
venv/
env/
# Data files (don't bake large datasets into images)
data/
*.csv
*.parquet
*.xlsx
# Jupyter
.ipynb_checkpoints/
*.ipynb
...
# Tests
tests/
pytest_cache/
.coverage
...
# Secrets — never let these into an image
.env
*.pem
*.keyThis causes a substantial reduction in the data sent to the Docker daemon before the build even starts. On large data projects with parquet files or raw CSVs sitting in the project folder, this can be the single biggest win of all five practices.
There's also a security angle worth noting. If your project folder contains .env files with API keys or database credentials, forgetting .dockerignore means those secrets could end up baked into your image — especially if you have a broad COPY . . instruction.
Rule of thumb: Always add .env and any credential files to .dockerignore in addition to data files that don't need to be baked into the image. Also use Docker secrets** for sensitive data.
# Summary
**
None of these techniques require advanced Docker knowledge; they're habits more than techniques. Apply them consistently and your images will be smaller, your builds faster, and your deploys cleaner.
Practice
What It Fixes
Slim/Alpine base image
Ensures smaller images by starting with only essential OS packages.
Layer ordering
Avoids reinstalling dependencies on every code change.
Multi-stage builds
Excludes build tools from the final image.
Same-layer cleanup
Prevents apt cache from bloating intermediate layers.
.dockerignore
Reduces build context and keeps secrets out of images.
Happy coding!
Bala Priya C** is a developer and technical writer from India. She likes working at the intersection of math, programming, data science, and content creation. Her areas of interest and expertise include DevOps, data science, and natural language processing. She enjoys reading, writing, coding, and coffee! Currently, she's working on learning and sharing her knowledge with the developer community by authoring tutorials, how-to guides, opinion pieces, and more. Bala also creates engaging resource overviews and coding tutorials.
関連記事
今日のまとめ
AI日報で今日の重要ニュースをまとめ読み