寒い日が続きますね。朝晩の冷え込みが厳しく、温かい飲み物が手放せない季節になりました。こんな時期は、仕事終わりにコクのあるインペリアルスタウトをゆっくり味わう時間が、1日の区切りになっています。ただ、毎回高アルコールだときついので、早く暖かくなって大好きなサワーエールをグーっと飲み干したい今日この頃ですが、皆様はいかがお過ごしでしょうか?
さて、Docker でアプリを運用していると、イメージをどこまで削るかは一度は悩むテーマだと思います。
この記事では、次の2つのローカル開発環境を題材に Distroless 化するときに押さえておきたい設計ポイントを整理します。
distroless-mt は Movable Type を PSGI (Starman) で動かす構成、distroless-wp は WordPress を FastCGI (PHP-FPM) で動かす構成です。
どちらもWebサーバーとアプリ実行環境を Distroless 化しています。
Distrolessとは
Distroless は、実行に必要なものだけを残したコンテナイメージです。言い換えると、アプリを動かすために不要なものを最初から持ち込まない設計です。
残すものは、実行バイナリ・実行時ライブラリ・証明書など最低限のシステム設定。逆に、sh や bash、apt や apk、汎用デバッグツールは原則いれません。
必要なバイナリ・ライブラリだけを残すことで、実行に必要のないそれらの脆弱性の影響を抑えることができます。
Distroless の価値は、単にイメージサイズが小さくなることだけではなく、実行責務を絞り、依存を明示できるところにあります。
今回の構成
distroless-mt
主要サービスは movabletype、webserver、database、phpmyadmin、mail です。
webserver は配信とプロキシ、movabletype はアプリ実行という形で責務を分離しています。
最短で試す場合は、distroless-mt ディレクトリで次の順に実行します。
-
files/movabletype に Movable Type のソース(`MT-8.8.2.zip`など)を保存
-
.env の MT_SOURCE_ZIP を保存したソースファイル名に変更
-
make prepare-mt
-
docker compose up -d --build
起動後の主な接続先は次のとおりです。
- http://localhost:8080(サイト)
- http://localhost:8080/mt/admin(Movable Type 管理)
プラグイン追加や修正は、ホスト側の www/movabletype/mt-static, www/movabletype/mt-templates, www/movabletype/plugins を編集して反映します。
mt-static や同梱プラグインを Movable Type の zip から取り直したい場合は、「make prepare-mt」を再実行します。
詳細は README.md を参照してください。
distroless-wp
主要サービスは php、wp-cli、webserver、database、phpmyadmin、mail です。
こちらも webserver は配信とプロキシ、php はアプリ実行という形で責務を分離しています。また WordPress 本体は /var/www/html/wp、可変データは /var/www/html/content に分け、運用時に触る対象を明確にしています。
最短で試す場合は、distroless-wp ディレクトリで次を実行します。
- docker compose up -d --build
起動後の主な接続先は次のとおりです。
- http://localhost:8080(サイト)
- http://localhost:8080/wp/wp-admin/(WordPress 管理)
プラグインやテーマの追加・修正は、ホスト側の www/content/plugins や www/content/themes を直接編集します。
WP-CLI で管理する場合は、「docker compose exec wp-cli wp plugin list」のように wp-cli サービス経由で実行できます。
詳細は README.md を参照してください。
なお、どちらの構成でも database、phpmyadmin、mail は Distroless 化していません。
database は本番でマネージドサービスを使う前提、phpmyadmin と mail はローカル開発用の補助ツールという位置付けです。
実装の勘所
両リポジトリの Dockerfile は、ほぼ同じ型で作っています。
builder ステージで必要物を作り、実行に必要なものだけを Distroless の final ステージへ渡します。
ここで効いてくるのがマルチステージビルドです。
Distroless にはパッケージマネージャやビルドツールを残さないので、単一ステージだと依存解決やコンパイル用のツールまで最終イメージに残ってしまいます。
builder と final を分けることで、この混入を防ぎます。
実際の手順は次のとおりです。
- builder ステージで必要な実行物を用意する
- 実行時ファイルを /opt に集約する
- ldd で必要な共有ライブラリを洗い出して同梱する
- final ステージを gcr.io/distroless/base-debian13 に揃える
- 「COPY --from=builder /opt /」で最終イメージを組み立てる
この型の良い点は、何がないと動かないかが Dockerfile から読み取れることです。
プロジェクト固有で見ると distroless-mt は DBD::mysql を含む Perl 依存を builder で解決し、Starman 実行に必要な要素だけを final に移しています。
distroless-wp は PHP-FPM と WP-CLI を Distroless 化し、WordPress の core・content・config を分離しやすい構成にしています。
WP-CLI 用イメージだけは、起動時スクリプト実行の都合で BusyBox 由来の最小限の sh を同梱しています。
メリット
- 依存関係が可視化される
実行に必要なファイルとライブラリがはっきりするので、障害時の切り分けが速くなります。 - セキュリティ運用が整理しやすい
不要パッケージを減らせるため、脆弱性対応の対象を絞りやすくなります。 - 環境差分を抑えやすい
たまたま入っていたツールへの依存が減るので、再現性が上がります。
デメリット
- シェルがない
障害時にコンテナへ入って調べる従来のやり方は使えません。原因究明はログ、ヘルスチェック、メトリクス中心に発想を切り替える必要があります。 - 観測設計が弱いと初動が重い
どこまで観測できる状態で運用に入るかが、そのまま調査スピードに効きます。 - Dockerfile の保守コストは増える
マルチステージ構成、依存ライブラリ抽出、実行時ファイルの選別を継続的に見直す必要があります。
導入時の注意点
まず ldd の収集漏れには注意が必要です。Distroless ではライブラリ不足がそのまま起動失敗になります。
次に、設定とデータの置き場所を最初に決めることです。環境依存の設定や可変データはイメージに焼き込まず、Compose 側で持つほうが運用しやすくなります。
最後に、初回ビルド時間は見込んでおくほうが安全です。ネイティブ拡張や多段ビルドを含む構成では、どうしても時間がかかります。
まとめと参考
Distroless の本質は、サイズ削減そのものより、実行責務を明確にすることにあります。
distroless-mt と distroless-wp のように web、app、data を分けて設計しておくと、ローカル開発でも本番に近い形を保ちやすくなります。
参考
- GoogleContainerTools/distroless
- Chainguard Academy: Getting Started with Distroless
- g-gen blog: Distroless Container Security
現場からは、以上です。