Dockerイメージ軽量化の完全ガイド|full・slim・alpine・distroless・scratch の使い分け
「Docker イメージを 小さくしたい」——これは単にディスク容量の話ではありません。軽量化は 起動速度・レジストリ転送コスト・攻撃対象の最小化・CI効率 すべてを同時に改善する、Docker運用の最終仕上げです。
この記事では、ベースイメージ選択の基準(full / slim / alpine / distroless / scratch)と、実際にサイズを削るテクニックを、落とし穴も含めて総合的に解説します。3-4で作った Flask アプリを 130MB → 50MB以下まで削る実例も示します。
目次
- なぜ軽量化するのか(5つの理由)
- ベースイメージの選択肢マップ
- alpine の仕組みと注意点
- distroless(Googleの「アプリだけ」イメージ)
- scratch(究極のゼロベース)
- 軽量化テクニック一覧
- 実例:Flaskアプリを 130MB → 50MB に
- サイズの測り方・分析ツール
- 軽量化の落とし穴
- まとめ
1. なぜ軽量化するのか(5つの理由)
| 理由 | 具体的な影響 |
|---|---|
| ① 起動速度 | レジストリからのpullが速い → オートスケール時の応答性向上 |
| ② 転送コスト | CI/CD、デプロイ、エッジ配布の帯域とストレージを削減 |
| ③ 攻撃対象の最小化 | 不要なバイナリ・ライブラリが少ない=脆弱性の露出が減る |
| ④ イメージスキャン速度 | Trivy 等のCVEスキャンが高速に完了 |
| ⑤ ストレージコスト | レジストリ・ノードのディスク使用量削減 |
サイズ削減はコスト削減として語られがちですが、本質的な価値は セキュリティです。
bash・curl・apt などが入っていないイメージは、侵害されても攻撃者ができることが極めて限られます(第8章 セキュリティで詳説)。
2. ベースイメージの選択肢マップ
代表的なベースイメージの特性を整理します。Python を例にした比較です。
| 種別 | 代表タグ | サイズ | 同梱 | 向き |
|---|---|---|---|---|
| full | python:3.12 |
約 1GB | Debian full、開発ツール多数 | 開発時・デバッグ用 |
| slim | python:3.12-slim |
約 130MB | Debian最小、python+標準lib | 本番のデフォルト候補 |
| alpine | python:3.12-alpine |
約 55MB | Alpine Linux (musl libc) | 軽量最優先・要注意 |
| distroless | gcr.io/distroless/python3-debian12 |
約 50MB | Pythonランタイムのみ(shellなし) | 本番・セキュリティ重視 |
| scratch | scratch |
0 バイト | 何もない | 静的バイナリ専用(Go等) |
選択フローチャート
│ 完全な静的バイナリを作れる? │
│ (Go, Rust 等) │
└────┬────────────────┬───────┘
│ YES │ NO
▼ ▼
┌──────┐ ┌─────────────────────────┐
│scratch│ │ Pythonのような動的ランタイム │
└──────┘ └─────┬───────────┬───────┘
│ │
セキュリティ最優先? 軽量最優先?
│ │
▼ ▼
┌──────────┐ ┌──────┐
│distroless│ │alpine│
└──────────┘ └──────┘
↑ │
│ │
│ musl互換性に問題?
└──── YES───┘
│ NO
▼
┌──────────┐
│ slim │
│(無難な中間)│
└──────────┘
3. alpine の仕組みと注意点
Alpine Linux は musl libc と BusyBox を使った超軽量ディストリビューション(約 5MB)です。Dockerイメージのベースとして広く使われていますが、従来のLinuxと互換性が完全ではない点に注意が必要です。
alpine のメリット
- 極めて小さい(ベース約 7MB、python:alpine でも 55MB)
- 攻撃対象面積が狭い
- セキュリティアップデートが頻繁
alpine のデメリット・注意点
| 注意点 | 詳細 |
|---|---|
| musl libc と glibc の非互換 | glibc 前提のバイナリ・ライブラリが動かないことがある |
| pip のビルド時間が長い | 多くのPythonホイールがmanylinux(glibc)向けで、alpine ではソースからビルドしなおし |
| DNSまわりの挙動差 | musl の getaddrinfo は /etc/resolv.conf の扱いが glibc と異なる |
| 時刻・ロケール | tzdata・locale が別途インストールが必要な場合がある |
パッケージマネージャが apk |
apt-get とは記法が異なる |
Python の pip で numpy・pandas・Pillow などを alpine にインストールすると、ホイールが存在せずソースビルドになるため、ビルド時間が10倍以上かかることがあります。Pythonなら slim の方が結果的に速くて小さいケースもあります。思い込みで alpine を選ばず、実測で比較しましょう。
apk(Alpine のパッケージマネージャ)の使い方
# apt-get update && apt-get install -y foo の代わりに:
RUN apk add --no-cache foo
# ビルド専用パッケージの一時導入(alpine特有の便利機能)
RUN apk add --no-cache --virtual .build-deps gcc musl-dev \
&& pip install psycopg2 \
&& apk del .build-deps
# ^ ビルド後に .build-deps をまるごと削除できる
4. distroless(Googleの「アプリだけ」イメージ)
distroless は Google が提供するベースイメージで、アプリとそのランタイムライブラリのみを含みます。shell(bash/sh)・curl・ls すらありません。
distroless の特徴
- サイズが小さい(Pythonで 約 50MB)
- shellがないのでexec攻撃がほぼ不可能
- パッケージマネージャもないので勝手にインストールされない
- root ではなく nonroot ユーザーがデフォルト
使用例(マルチステージとの組み合わせ)
# ===== ビルド =====
FROM python:3.12-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
COPY . .
# ===== 実行(distroless)=====
FROM gcr.io/distroless/python3-debian12:nonroot
WORKDIR /app
# pip の --user でインストールしたパッケージをコピー
COPY --from=builder /root/.local /home/nonroot/.local
COPY --from=builder /app /app
ENV PATH=/home/nonroot/.local/bin:$PATH
CMD ["app.py"]
distroless は
:debug タグにすると BusyBox シェルが入るため、トラブルシューティング時だけ debug に切り替えるという運用ができます(例:gcr.io/distroless/python3-debian12:debug)。本番は shell なし、調査時だけ debug、が鉄則。
5. scratch(究極のゼロベース)
scratch は「何もない」特別なイメージで、サイズは 0バイトです。静的バイナリにコンパイルできる言語(Go・Rust)でのみ実用的です。
# Go アプリを scratch で動かす最小構成
FROM golang:1.22-alpine AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /bin/app
FROM scratch
# TLSを使うなら ca-certificates を明示的にコピー
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /bin/app /app
ENTRYPOINT ["/app"]
# 最終イメージ: Goバイナリ(約5MB)+ CA証明書(約200KB)= 約5MB
scratch の注意点
- shell、libc、何もないので
docker execでコマンドを打つことすらできない - 動的リンクのバイナリは絶対に動かない(静的のみ)
- HTTPS通信するなら CA証明書を自分でコピーする必要がある
- タイムゾーン情報 (
/usr/share/zoneinfo) も必要なら明示的に
6. 軽量化テクニック一覧
| テクニック | 効果 | 適用章との関連 |
|---|---|---|
| slim / alpine / distroless ベースに変更 | 数百MB単位の削減 | 本章 |
| マルチステージビルド | ビルドツールを除外 | 3-5 |
RUN の && 連結+キャッシュ削除 |
中間ファイル削減 | 3-2 |
--no-install-recommends(apt) |
推奨パッケージを入れない | 本章 |
rm -rf /var/lib/apt/lists/* |
aptキャッシュ削除 | 本章 |
pip install --no-cache-dir |
pip キャッシュを残さない | 本章 |
Go: -ldflags="-s -w" |
シンボル情報を削除(バイナリ縮小) | 本章 |
Go: UPX 圧縮 |
バイナリをさらに圧縮(起動時に展開) | 本章 |
| .dockerignore | 不要ファイルを含めない | 3-3 |
| イメージから不要ツール削除 | gcc, make, dev-libs 等を build-stage のみに | 3-3 |
apt のキャッシュ削除パターン
RUN apt-get update && \
apt-get install -y --no-install-recommends \
curl \
ca-certificates && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
7. 実例:Flaskアプリを 130MB → 50MB に
3-4 で作った Flask アプリ(slim ベース、130MB)を distroless で書き直してみます。
# ===== ビルドステージ =====
FROM python:3.12-slim AS builder
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends \
gcc libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
# --user で /root/.local 以下に集約(後で distroless にコピーしやすい)
RUN pip install --user --no-cache-dir -r requirements.txt
COPY . .
# ===== 実行ステージ(distroless)=====
FROM gcr.io/distroless/python3-debian12:nonroot
WORKDIR /app
# 依存ライブラリ(/root/.local → /home/nonroot/.local)
COPY --from=builder /root/.local /home/nonroot/.local
# アプリコード
COPY --from=builder /app /app
ENV PATH=/home/nonroot/.local/bin:$PATH
ENV PYTHONPATH=/home/nonroot/.local/lib/python3.12/site-packages
EXPOSE 8000
CMD ["-m", "gunicorn", "--bind", "0.0.0.0:8000", "app:app"]
# 最終サイズ: 約 50MB
| バージョン | ベース | サイズ | shell | セキュリティ |
|---|---|---|---|---|
| v4(3-5完成形) | python:slim | 約 130MB | あり | 中 |
| v5(本記事) | distroless | 約 50MB | なし | 高 |
distroless は shell がないため、
docker exec -it myapp bash のようなデバッグができません。トラブルシューティングはログ・ヘルスチェック・メトリクスに頼る必要があります。ログが整備されていないチームは、まず slim で本番運用して知見を貯めてから distroless に移行するのが安全です。
8. サイズの測り方・分析ツール
基本:docker images
$ docker images myapp
REPOSITORY TAG IMAGE ID CREATED SIZE
myapp v1 abc123 2 minutes ago 1.02GB
myapp v5 def456 1 minute ago 52MB
レイヤー単位の分析:docker history
$ docker history myapp:v5 --human --format "table {{.CreatedBy}}\t{{.Size}}"
CREATED BY SIZE
CMD ["-m" "gunicorn" ...] 0B
ENV PYTHONPATH=/home/nonroot/.local/lib/... 0B
COPY /app /app 3.2kB
COPY /root/.local /home/nonroot/.local 38MB ← ここが重い
...
詳細分析:dive(推奨)
dive は各レイヤーの中身を視覚的に調べられるツールです。
# インストール(Linux/macOS)
brew install dive
# 使い方
dive myapp:v5
# → レイヤーごとに何が追加・変更・削除されたか可視化される
9. 軽量化の落とし穴
| 落とし穴 | 症状 | 対策 |
|---|---|---|
| alpine 盲信でビルド時間増大 | pip でnumpyをソースビルド → CIが20分に | slim と実測で比較、alpineが常に正解ではない |
| scratch で HTTPS が動かない | “x509: certificate signed by unknown authority” | CA証明書をbuildステージから COPY |
| distroless でデバッグができない | コンテナ内でコマンドを打てない | :debug タグに切替 or ログ整備 |
apt-get install の後でクリーンしていない |
イメージが数百MB肥大化 | rm -rf /var/lib/apt/lists/* 必須 |
| タイムゾーンが UTC のまま | ログのタイムスタンプがずれる | tzdata を入れて TZ 環境変数設定 |
latest タグで alpine が更新された結果、互換性問題 |
ある日突然ビルドが壊れる | タグをバージョン固定(alpine:3.19) |
| イメージは小さいがメモリ使用量は減らない | 「軽い=省メモリ」ではない | メモリ削減は第9章 リソース制限で別途対応 |
10. まとめ
軽量化はサイズ数字を小さくすること自体が目的ではなく、起動速度・セキュリティ・運用コストを改善する手段です。適切なベースイメージ選びと、マルチステージ・レイヤーキャッシュ・.dockerignore の3つを組み合わせると、本番品質の最小イメージが作れます。
| シナリオ | 推奨ベース | 理由 |
|---|---|---|
| 初めてのDockerfile・開発環境 | full |
デバッグツールが揃っている |
| 一般的なWebアプリ(本番) | slim |
互換性◎・サイズ◎のバランス |
| サイズ最優先・glibc依存なし | alpine |
最小。ただしPythonは要注意 |
| セキュリティ最優先(本番) | distroless |
shell なし、攻撃面最小 |
| Go/Rustの静的バイナリ | scratch |
究極の軽さ(5MB〜) |
これで第3章「Dockerfileによるイメージ構築」は完了です。3-1 で文法、3-2 でキャッシュ、3-3 でマルチステージ、3-3 で .dockerignore、3-5 で実践、3-6 で軽量化——本番運用可能なDockerfileを書く全要素が揃いました。
第4章「データの永続化」では、コンテナのエフェメラル性(消えてしまう性質)に向き合い、ボリューム・バインドマウント・tmpfs を使ってデータを守る運用技術を解説します。アプリと同じくらい重要なテーマです。
参考リンク
- Alpine Linux 公式 — musl libc と apk パッケージマネージャの公式情報。
- distroless(GitHub) — Google公式の distroless イメージ一覧と使用例。
- scratch イメージ — 公式の「空」ベースイメージ。
- dive(レイヤー分析ツール) — イメージの中身を視覚的に調べる定番ツール。
- Dockerfile best practices — Docker公式の総合ベストプラクティス。



コメント