イメージ肥大化を防ぐ
レイヤー構成・.dockerignore・ベースイメージ選定の 3 つの観点から、コンテナイメージを小さく保つためのレシピをまとめます。
なぜサイズを気にするのか
イメージが大きいことのデメリットは、単なるディスク節約の話ではありません。
- pull / push が遅い: CI のデプロイ時間に直結する。数百 MB と数 GB では分単位で差が出る
- コールドスタートが遅い: Kubernetes / ECS で新ノードに配るたびに pull が走る
- 攻撃面が広い: 不要なライブラリが増えるほど、脆弱性スキャン件数も増える
- レジストリのコスト: AWS ECR など有償レジストリはストレージと転送に料金がかかる
「本番のクリティカルパスで pull → 起動」という構成の場合、イメージサイズは可用性指標にも直結します。
レイヤーの仕組みを踏まえた書き方
Docker イメージは命令ごとに積み重なるレイヤーの集合で、レイヤー内で削除しても、前のレイヤーに残ったバイトは消えないのがポイントです。
NG 例:
RUN apt-get update && apt-get install -y build-essential
RUN pip install -r requirements.txt
RUN apt-get remove -y build-essential # 前のレイヤーは消えない
OK 例(同じレイヤーで削除):
RUN apt-get update \
&& apt-get install -y --no-install-recommends build-essential \
&& pip install -r requirements.txt \
&& apt-get purge -y build-essential \
&& apt-get autoremove -y \
&& rm -rf /var/lib/apt/lists/*
まとめて && で繋ぎ、最後にキャッシュを削除するのが基本形。
もっとクリーンに分離するなら、マルチステージビルドでビルドツールを別ステージに追い出す方法が最適解です。
.dockerignore で余計なものを混ぜない
.dockerignore は Docker の世界でしばしば過小評価されます。
忘れて困るのは次の通り。
.git: 履歴まるごと入ると数十 MB〜数 GB 増えるnode_modules/.venv: ホストのビルド済み依存がイメージに紛れる(OS 違いで動かない可能性も).env/secrets/: 認証情報がイメージに焼き込まれる 最大の事故要因*.log/tmp//coverage/: 実行時に生成される大量ファイルDockerfile/.dockerignore自身
# .dockerignore 例
.git
.gitignore
node_modules
.venv
__pycache__
coverage
.env
.env.*
tmp
*.log
Dockerfile*
.dockerignore
docker build は .dockerignore に従ってビルドコンテキストを tar に詰めるので、不要ファイルを除外するほどビルド自体も速くなる副次効果があります。
ベースイメージ: alpine / slim / distroless
同じ Node.js 20 のベースでもバリエーションでサイズは桁違いです。
| タグ | 概要 | 目安サイズ |
|---|---|---|
node:20 |
フル Debian、ビルドツール込み | ~1.1 GB |
node:20-slim |
Debian 最小構成 | ~240 MB |
node:20-alpine |
Alpine Linux (musl libc) | ~140 MB |
gcr.io/distroless/nodejs20-debian12 |
シェルも package manager も無い | ~180 MB |
選び方の指針:
- alpine: サイズ最小だが glibc ではないため、ネイティブ拡張や glibc 前提のバイナリで動かないことがある
- slim: サイズと互換性のバランス。迷ったらここから
- distroless: 実行時に
shもaptもないので攻撃面が最小。デバッグは:debugタグやサイドカーでやる
「開発時は node:20 や -slim、本番は distroless」のようにマルチステージで使い分けるのが実践的です。
BuildKit のキャッシュと secrets を活用する
現在の Docker は既定で BuildKit が有効で、次の機能でビルドとイメージの両方が効率化できます。
# syntax=docker/dockerfile:1.7
FROM python:3.12-slim
# パッケージキャッシュを保持する(イメージには含めない)
RUN --mount=type=cache,target=/root/.cache/pip \
pip install -r requirements.txt
# secrets を渡す(履歴・イメージに残らない)
RUN --mount=type=secret,id=pip_token \
PIP_INDEX_URL=https://user:$(cat /run/secrets/pip_token)@registry.example.com/pypi/simple \
pip install -r requirements-private.txt
docker build --secret id=pip_token,src=./pip_token.txt -t my-app .
--mount=type=cache を使えば、pip / apt / npm のキャッシュをイメージに含めずに再利用でき、ビルドが速くなりつつサイズも抑えられます。
--mount=type=secret は認証トークンを渡すための仕組みで、ARG や ENV と違い イメージに残りません。
計測と継続的な監視
現状把握とリグレッション検知のためのツールを紹介します。
docker image ls: 単純なサイズ確認docker history <image>: レイヤーごとのサイズ。どこで膨らんだかを特定できる- dive: レイヤーをツリー状に可視化し、無駄なファイルを見つけやすい
- CI での自動チェック:
docker image inspectでサイズを取り、閾値を超えたら失敗させる - 脆弱性スキャン: Trivy / Grype などでベースイメージの脆弱性を検知。サイズ削減と並行して実施
**「小さくしてもすぐ太る」**ので、新しい依存を入れたら docker history で増分を確認する習慣をつけると、イメージは長期的に小さく保てます。
