DockerfileのFROM設計|debian・alpine・distrolessのベースイメージを比較して最適解を選ぶ方法

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Docker > DockerfileのFROM設計|debian・alpine・distrolessのベースイメージを比較して最適解を選ぶ方法
「FROM ubuntu:latestで動いてるし、まあいいか」──そう思い続けていませんか。
本番コンテナのセキュリティスキャンを初めて実行して、Critical CVEが数十件ヒットして青ざめた経験を持つエンジニアは少なくありません。その原因の多くが、Dockerfileのベースイメージ選択を深く考えていなかったことです。

この記事では、FROM命令で選べる主要なベースイメージ──debian・debian:slim・ubuntu・alpine・distroless・scratch──を実測データで比較し、アプリ種別ごとの選定ルールを整理します。
動作確認環境: Docker 27.3 / RHEL 9.4・Ubuntu 24.04 LTS

この記事のポイント

・FROM命令のベースイメージ選択でCVE数・サイズ・デバッグ性が決まる
・debian:slimはPythonアプリでの互換性とサイズのバランスが最良
・alpineはmusl libcでglibcバイナリが動かず選択前に要確認
・distrolessはCVEを最小化できるがシェルがなくデバッグ設計が必要


「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

ベースイメージ選択がDockerfileの品質を左右する理由

DockerfileのFROM命令は、「このコンテナに最初から積み込むOSの部品セットを選ぶ」命令です。

たとえばFROM debian:12と書くと、Debian 12(Bookworm)の最小インストール環境が丸ごと取り込まれます。その中にはglibcやbash、aptといった基本コンポーネントが含まれますが、同時にそれらのパッケージに含まれる既知の脆弱性(CVE)も一緒に持ち込まれます。

逆にFROM scratchと書けば、何も入っていない空のイメージから始まるため、CVEはゼロですがシェルもライブラリも存在しません。

ベースイメージを選ぶ基準は主に3つです。

・イメージサイズ: 展開後のサイズが大きいほど、レジストリへのpush/pull・コンテナ起動に時間がかかります。CI/CDパイプラインでは積み重なって大きなコストになります
・CVE数: ベースに含まれるパッケージが多いほど、脆弱性スキャンの結果が悪化します。セキュリティポリシーが厳しい環境では選定理由を説明できなければなりません
・デバッグ容易性: シェルやコマンドがないイメージはCVEが少ない反面、コンテナ内に入って調査することができません。観測性(Observability)の設計が必要になります

この3つのバランスをどこに置くかで、最適なベースイメージが変わります。Dockerfileを書く前に「本番のセキュリティ要件」と「運用中のデバッグ手順」を先に決めておくと、選択が迷いなくなります。

主要ベースイメージのサイズとCVE数を比較する

以下はdocker pull後にdocker image inspectで取得した展開サイズと、trivy image --severity HIGH,CRITICALでスキャンしたCVE件数の実測値です(trivy v0.55.0)。

スキャンするタイミングによってパッチ状況が変わるため、あくまで傾向値として参照してください。各イメージのベンダーがセキュリティパッチを適用するたびに数値は変動します。
イメージ 圧縮サイズ 展開サイズ HIGH+CRITICAL CVE(目安)
ubuntu:24.04 約29MB 約78MB 5~20件
debian:12 約52MB 約137MB 10~30件
debian:12-slim 約27MB 約74MB 3~15件
alpine:3.20 約3.7MB 約7.4MB 0~2件
gcr.io/distroless/base-debian12 約20MB 約49MB 0~3件
scratch 0MB 0MB 0件

1. debian:12とdebian:12-slim

debian:12(Bookworm)は、多くのツールが最初から入っている「フルDebian」です。ドキュメント、マニュアルページ、各種ユーティリティが揃っているため、インタラクティブな作業環境としては使いやすい反面、サイズが大きくCVEも多めです。

debian:12-slimは不要なファイルを除いた最小構成です。aptは使えるためパッケージの追加インストールは可能ですが、curlやwgetはデフォルトでは含まれません。フルDebianとの互換性を保ちつつサイズを半分近くに抑えられるため、PythonアプリやJavaアプリのデフォルト候補として広く使われています。

# debian:12-slimベースのPythonアプリDockerfile例 FROM debian:12-slim RUN apt-get update && apt-get install -y --no-install-recommends \ python3 python3-pip \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt COPY . . CMD ["python3", "app.py"]

apt-get installの後にrm -rf /var/lib/apt/lists/*を付けてキャッシュを削除するのがセットです。これを省略するとaptのキャッシュがレイヤーに残り、数十MBが無駄に膨らみます。

2. ubuntu:24.04

ubuntu:24.04はDebianをベースにしたUbuntu公式イメージです。aptが使え、Debianとほぼ同じ操作感で扱えます。ただしDebianより多くのパッケージが含まれており、slimより大きめのサイズになります。

Ubuntu独自のパッケージ(software-properties-commonなど)が必要な場合や、チームがUbuntuに慣れている場合に選択します。DockerfileをRHEL/Rocky系の実機サーバーと共存させる場合は、パッケージ名の差異(python3-dev vs python3-devel)に注意してください。

3. alpine:3.20

Alpineは圧倒的な小ささが特徴です。musl libcとbusyboxを組み合わせた独自の最小Linuxで、展開後も7MB台に収まります。セキュリティスキャンの結果もクリーンになりやすく、一見理想的に見えます。

ただし、musl libcとglibc(GNU C Library)の非互換という落とし穴があります。次の「alpineを使う時に必ずハマるmusl libc問題」セクションを先に確認してから採用を検討してください。

4. gcr.io/distroless/base-debian12(Distroless)

Googleが提供するDistrolessイメージは、シェル・パッケージマネージャー・デバッグツールを一切含まない最小構成です。アプリの実行に必要なランタイムライブラリだけを含めるため、CVEが激減します。

利用可能なイメージの種類は用途別に分かれています。

・gcr.io/distroless/base-debian12: glibc・ssl・tzdataだけの最小ベース
・gcr.io/distroless/python3-debian12: Python 3インタープリタ付き
・gcr.io/distroless/java21-debian12: JRE 21付き
・gcr.io/distroless/static-debian12: 静的バイナリ向け(glibc不要)
・gcr.io/distroless/cc-debian12: C/C++の動的リンクバイナリ向け

DistrolessはGitHubのGoogleContainerTools/distrolessリポジトリで管理されており、イメージはGCR(Google Container Registry)から取得します。

5. scratch

FROM scratchは何も含まない空のイメージです。Goなどで完全静的にコンパイルしたバイナリだけをCOPYすれば、シェルもライブラリも持たない超小型イメージが作れます。

CVEはゼロになりますが、コンテナに入ってデバッグすることが完全にできなくなります。本番用の静的バイナリ配布には有効ですが、観測性(ログのstdout集約・メトリクスエンドポイント)を設計してから採用するのが現実的です。

alpineを使う時に必ずハマるmusl libc問題

AlpineはMicrosoft・Googleが提供する公式言語イメージ(python:3.12-alpine・node:20-alpine)でも採用されているため、「小さいイメージ = alpine使えばいい」と思われがちです。しかし現場ではmusl libc起因のトラブルが後を絶ちません。

問題の核心: Alpineが使うmusl libcは、多くのLinuxバイナリが依存するglibcとABI互換がありません。コンパイル済みのglibcバイナリをAlpineに持ち込もうとすると、共有ライブラリが見つからずに起動に失敗します。

# alpine上でglibcバイナリを実行しようとした時のエラー例 $ ./myapp sh: ./myapp: not found # ライブラリの依存を確認(ビルド元ホストでの確認例) $ ldd ./myapp linux-vdso.so.1 (0x...) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x...) # alpineには /lib/x86_64-linux-gnu/libc.so.6 が存在しない

Pythonでは、PyPIのWheel(.whl)ファイルがmanylinux(glibc依存)でビルドされているケースがよくあります。Alpineでは対応するWheelが見つからず、ソースからのビルドにフォールバックします。numpyやcryptographyなどで起こると、ビルドに数十分かかったり、コンパイラが足りずにエラーになったりします。

# alpine上でnumpyをpip installしようとした時のよくあるエラー $ pip install numpy ... ERROR: Could not build wheels for numpy, which is required to install... error: command '/usr/bin/gcc' failed with exit code 1 # 対処: apkでビルドツールを追加する(イメージが大きくなる) RUN apk add --no-cache gcc musl-dev python3-dev

ビルドツールを追加するとalpineのサイズ優位性が薄れます。このパターンに陥ったら、alpineを使い続けるよりdebian:12-slimに変更するほうが根本解決になります。

alpineが向いているケース:
・シェルスクリプトとalpineネイティブパッケージだけで完結するツール
・glibcへの依存がない純粋なGoバイナリやRustバイナリ(静的ビルド)
・外部ネイティブ拡張に依存しないシンプルなNode.jsアプリ

alpineを避けるべきケース:
・Python(特にnumpy・pandas・scikit-learn等の科学系ライブラリ)
・Javaアプリ(JREはglibc前提)
・ネイティブ拡張を多用するRubyアプリ

distrolessイメージとシェルなし運用の設計指針

Distrolessイメージはセキュリティを重視する現場で採用が増えています。シェルがないためdocker exec -it コンテナ名 bashが使えず、デバッグ方法を事前に設計しておく必要があります。

1. マルチステージビルドで開発用と本番用を分ける

Distrolessを本番ステージに使い、ビルドや開発ステージには通常のイメージを使います。マルチステージビルドとの組み合わせが前提になります。

# ビルドステージ: debian:12-slimでビルド・依存インストール FROM debian:12-slim AS builder RUN apt-get update && apt-get install -y --no-install-recommends \ python3 python3-pip python3-venv \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY requirements.txt . RUN python3 -m venv /opt/venv ENV PATH="/opt/venv/bin:$PATH" RUN pip install --no-cache-dir -r requirements.txt COPY . . # 本番ステージ: distroless(シェルなし・最小CVE) FROM gcr.io/distroless/python3-debian12 COPY --from=builder /opt/venv /opt/venv COPY --from=builder /app /app ENV PATH="/opt/venv/bin:$PATH" WORKDIR /app CMD ["app.py"]

2. デバッグ時はdebugタグを使う

DistrolessにはBusyBoxシェル付きの:debugタグが用意されています。本番では:latest(またはバージョン固定)を使い、問題発生時は一時的に:debugタグのイメージに切り替えてデバッグします。

# 通常本番用: シェルなし FROM gcr.io/distroless/python3-debian12 # デバッグ時のみ: busyboxシェルあり(本番には戻すこと) FROM gcr.io/distroless/python3-debian12:debug # debugタグのコンテナに入る方法 $ docker exec -it mycontainer /busybox/sh

3. 観測性の事前設計

シェルなし運用ではログをstdout/stderrに集約し、docker logsやFluentd等で外部収集することが前提になります。コンテナ内のファイルに書き込んでdocker execで取り出す運用は、Distrolessでは設計上選べません。アプリ側のログ設計(structlogやjson形式のstdout出力)をDockerfile設計と同時に決めておきましょう。

マルチステージビルドとベースイメージ選定の組み合わせ

ベースイメージの選択を語る上で、マルチステージビルドとの組み合わせは避けて通れません。典型的なパターンは「ビルドステージに重いイメージ、リリースステージに軽いイメージ」を使うことです。

Goで書いたアプリを例にすると、ビルドステージではgolang:1.23-bookwormを使いますが、最終成果物は静的バイナリ1つなので、リリースステージにscratchやgcr.io/distroless/static-debian12を使えます。

コンテナを本番環境に安全に展開するための設計全体を体系的に学びたい方には、Dockerハンズオン講座でコンテナ設計の「型」を習得する方法もあります。

# Goアプリの典型的なマルチステージビルド FROM golang:1.23-bookworm AS builder WORKDIR /src COPY go.mod go.sum ./ RUN go mod download COPY . . # 静的ビルド(CGO無効化 + 外部ライブラリなし) RUN CGO_ENABLED=0 GOOS=linux go build -o /app ./cmd/server # リリースステージ: distroless static(CVEほぼゼロ) FROM gcr.io/distroless/static-debian12 COPY --from=builder /app /app ENTRYPOINT ["/app"]

PythonやJavaのように実行時にも重いランタイムが必要なアプリでは、完全なscratchは使えません。その場合はdistroless/python3やdistroless/java21を選びます。ビルドステージとリリースステージで異なるベースイメージを組み合わせるのがDockerfile設計の基本です。

ビルドステージの重いイメージはCIビルド時にしか使われないため、最終イメージには含まれません。つまり「ビルド環境の充実度」と「本番イメージの軽量さ・安全性」を両立できるのがマルチステージビルドの強みです。

ベースイメージのバージョン固定とタグ設計

FROM debian:latestのように:latestタグを使うと、ビルドする時期によって異なるバージョンのイメージが取得されます。開発環境では動いていたのに、1ヶ月後にCI/CDでビルドすると壊れた──というトラブルの典型的な原因です。

1. バージョンタグを必ず固定する

# NG: latestは使わない FROM debian:latest # OK: バージョンを固定 FROM debian:12.7-slim # 言語ランタイムのイメージも同様に固定 FROM python:3.12.7-slim-bookworm FROM golang:1.23.2-bookworm FROM node:22.11.0-bookworm-slim

2. 厳密な再現性が必要ならダイジェスト固定

バージョンタグは同じタグに異なるイメージが上書きされる(タグの再利用)可能性がゼロではありません。ビルドの完全な再現性を担保したい場合は、イメージのダイジェスト(SHA256ハッシュ)で固定します。

# ダイジェスト固定(これより厳密な固定はない) FROM debian:12-slim@sha256:a1b2c3d4e5f6...(実際のハッシュ値に置き換える) # ダイジェストの確認方法 $ docker pull debian:12-slim $ docker inspect --format='{{index .RepoDigests 0}}' debian:12-slim debian@sha256:a1b2c3d4e5f6...

CI/CDパイプラインでは、定期的にベースイメージを最新バージョンに更新してリビルドする仕組み(Dependabotやrenewbotによる自動PR等)と組み合わせると、バージョン固定の恩恵を受けつつセキュリティパッチを取り込み続けられます。

用途別ベースイメージ選定の判断フロー

ここまでの内容を整理して、アプリ種別ごとの推奨ベースイメージをまとめます。
アプリの種類 ビルドステージ リリースステージ 理由・注意点
Goバイナリ(静的ビルド) golang:1.23-bookworm gcr.io/distroless/static-debian12 または scratch 静的バイナリのみ必要。CVE最小化できる
Pythonアプリ(科学系・pip依存多) python:3.12-slim-bookworm python:3.12-slim-bookworm manylinuxホイール依存でglibc必須。alpineはNG
Pythonアプリ(標準ライブラリ中心) python:3.12-slim-bookworm gcr.io/distroless/python3-debian12 ランタイム内包でCVE少・セキュリティ重視
Node.jsアプリ node:22-bookworm-slim node:22-bookworm-slim ネイティブ拡張があればalpineは危険
Javaアプリ(JREのみ) eclipse-temurin:21-jdk-noble gcr.io/distroless/java21-debian12 JREはglibc前提。distrolessで最小化
シェルスクリプトツール 不要(ステージ1本) alpine:3.20 busyboxで十分。musl問題なし
判断に迷ったらdebian:12-slimを選んでおくのが現場の経験則です。glibc互換性の問題もなく、apt経由でパッケージを追加でき、トラブルが少ない選択肢です。後からセキュリティ要件が厳しくなったタイミングでdistrolessへ移行するのが、無理のない段階的な改善です。

トラブルシュート|よくあるエラーと対処法

1. 「exec format error」が出る

standard_init_linux.go:228: exec user process caused: exec format error

CPUアーキテクチャの不一致で起こります。Apple Silicon(ARM64)のMacでビルドしたイメージをx86_64のサーバーで動かそうとした場合などです。docker buildxでターゲットアーキテクチャを指定してリビルドしてください。

# x86_64(linux/amd64)向けにクロスビルド $ docker buildx build --platform linux/amd64 -t myapp:latest .

2. distrolessにCOPYしたバイナリが起動しない

# distroless/staticにglibcバイナリをCOPYした場合の起動エラー $ docker run --rm myapp /app: not found # 原因確認: バイナリのglibc依存をlddで確認 $ ldd ./app linux-vdso.so.1 libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 # glibc依存あり → distroless/staticはNG

gcr.io/distroless/static-debian12にglibcが必要なバイナリをCOPYすると起動に失敗します。glibcを含むgcr.io/distroless/base-debian12またはgcr.io/distroless/cc-debian12に変更してください。

3. alpineでpip installが失敗する

# alpineでcryptographyインストール時のエラー例 ERROR: Could not build wheels for cryptography # 対処1: ビルドツールを追加(サイズが増大する) RUN apk add --no-cache gcc musl-dev libffi-dev openssl-dev python3-dev # 対処2(推奨): ベースイメージをdebian:12-slimに変更する FROM python:3.12-slim-bookworm

ビルドツールを追加するとalpineのサイズ優位性が失われます。このパターンに陥ったら、alpineではなくdebian:12-slimに変更するほうが根本解決になります。

4. ベースイメージを更新してもCVEが残る

docker pull debian:12-slimでイメージを更新しても、ビルドキャッシュが残っているため古いレイヤーが使われることがあります。

# キャッシュを使わず完全再ビルド $ docker build --no-cache -t myapp:latest . # または最新イメージを明示的にpullして再ビルド $ docker build --pull -t myapp:latest .

本記事のまとめ

DockerfileのFROM命令でどのベースイメージを選ぶかは、「なんとなく動くから」で決めてよいものではありません。イメージサイズ・CVE数・デバッグ容易性という3つの軸で整理すると、用途ごとの最適解が見えてきます。
ベースイメージ サイズ CVE 主な用途
debian:12-slim 中(~74MB) 中 Python/Node/Rubyアプリのデフォルト。apt使用可
ubuntu:24.04 中(~78MB) 中 Ubuntuネイティブパッケージが必要な場合
alpine:3.20 小(~7MB) 少 シェルツール・静的バイナリ。glibcアプリには不向き
distroless 小~中 最少 セキュリティ要件が厳しい本番環境。シェルなし
scratch 0MB ゼロ 完全静的Goバイナリ。極限のサイズ削減
迷ったときはdebian:12-slimを選んでおくと、glibc互換性の問題もなく、aptでパッケージを追加でき、トラブルの少ない選択肢になります。セキュリティスキャン(trivyなど)を定期的に実行し、CVEが増えたタイミングでベースイメージを更新する運用習慣を付けておくと、本番コンテナの健全性を長期的に維持できます。
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、
DockerfileのFROM設計・マルチステージビルド・Composeを組み合わせたコンテナ設計の実践を、ハンズオン形式で学べる講座を開催しています。
Docker実践講座の詳細を見る>>

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。