Docker・インフラ構築

DockerイメージのAlpineとSlim:軽量化と互換性を巡る二つの選択肢

Dockerイメージを軽量化しようとすると、まず候補に挙がるのがAlpineバリアントとSlimバリアントです。どちらも通常のイメージより小さくできますが、同じ方法で小さくしているわけではありません。ここを理解しないままalpineへ切り替えると、イメージサイズは減ったのにビルド時間が伸びたり、依存ライブラリが動かなくなったりすることがあります。…

DockerイメージのAlpineとSlim:軽量化と互換性を巡る二つの選択肢

特にPythonやNode.js、PHPのようにネイティブ拡張を利用する環境では、docker イメージ alpine slim 違いを単なる容量比較として捉えないことが大切です。Alpineはmusl libc、Slimは主にDebianとglibcを基盤にしており、選択の差はファイルサイズだけでなく、バイナリ互換性、パッケージの入手性、ビルドの再現性、運用時の調査しやすさにまで広がります。

この記事では、AlpineとSlimがどのような仕組みで軽量化されているのかを確認しながら、Dockerfileの書き換えで起きやすい失敗と、個人開発で無理なく選ぶための考え方を整理していきます。

AlpineとSlimは、軽量化の方法が違う

最初に押さえておきたいのは、AlpineとSlimは「同じOSを別の圧縮率で配布したもの」ではないという点です。

AlpineバリアントはAlpine Linuxをベースにしています。ベースイメージ単体で5MB未満という極めて小さなサイズを実現しており、コンテナイメージの容量をできるだけ抑えたい場面では非常に魅力的です。パッケージ管理にはapkを使い、不要なツールやライブラリが最初から多く含まれていません。

一方、Slimバリアントは主にDebianをベースにした軽量版です。通常のDebian系イメージから、curlやgitなど、標準的には便利でも実行時には必須とは限らないツールを削減しています。それでもDebianのパッケージ管理であるaptを利用でき、glibcを中心とした一般的なLinux環境との互換性も維持されます。

つまり、Alpineは小さな基盤そのものを採用する選択であり、Slimは広い互換性を持つDebianの構成を絞り込む選択です。

比較項目AlpineバリアントSlimバリアント
ベースOSAlpine Linux主にDebian
標準Cライブラリmusl libcglibc
パッケージ管理apkaptapt-get
ベースイメージの特徴極めて小さい標準版より軽量で互換性を維持
パッケージの入手性Alpine向けパッケージが必要Debian系パッケージを利用しやすい
ネイティブ拡張ビルド環境の追加が必要になりやすい既存のバイナリを利用しやすい
調査・運用必要なツールを自分で追加する場面が多いDebian系の知識や事例を活用しやすい
向いている場面構成を細かく管理できる小さな実行環境互換性と再現性を優先する一般的なアプリ

容量だけを見るとAlpineが優勢に見えます。しかし、実際のプロジェクトでは、ベースイメージのサイズがそのまま最終的な開発効率やデプロイ速度になるとは限りません。アプリケーションの依存関係が大きければ、差が相対的に小さくなることもありますし、ビルドに必要なコンパイラや開発用ヘッダーを追加した結果、Alpineの小ささが目立たなくなる場合もあります。

軽いイメージを選ぶことと、軽く扱える開発環境を選ぶことは、同じではありません。

Alpineの小ささを支えるmusl libc

Alpineを選ぶときに最も重要な技術的な違いが、標準Cライブラリです。

一般的なDebianやUbuntuではglibcが使われています。Linux向けに配布されているバイナリや、各言語のパッケージが提供するホイール、つまりコンパイル済みのバイナリパッケージも、glibcを前提としていることが少なくありません。

Alpineではglibcではなくmusl libcが採用されています。muslは小さく、シンプルな構成を取りやすい一方で、glibc向けにビルドされたバイナリをそのまま実行できないことがあります。これはAlpineが不安定という話ではなく、そもそも実行環境の前提が異なるために起きる問題です。

たとえば、通常のDebianベースのイメージでインストールできていたライブラリが、Alpineに変更した途端に次のような状態になることがあります。

  • 対応するバイナリパッケージが見つからず、ソースコードからのビルドに切り替わる
  • コンパイラや開発用ライブラリが必要になり、Dockerfileが長くなる
  • ビルド時には成功しても、実行時に共有ライブラリが見つからない
  • glibc前提のバイナリを利用する処理でエラーが発生する
  • 同じ依存関係を指定しているのに、ビルド結果や処理時間が変わる

ここで注意してください。docker alpine glibc 互換性という観点では、互換用のパッケージを追加すればすべて解決する、と考えないほうが安全です。互換レイヤーを導入できるケースはありますが、アプリケーションが本当に必要としている機能まで完全に置き換えられるとは限りません。依存ライブラリのビルド条件や、実行時の動的リンクまで確認する必要があります。

Dockerfileでベースイメージを変更する作業は、見た目だけなら簡単です。たとえばpython:3.12-slim-bookwormpython:3.12-alpineへ変えるだけに見えます。しかし、この1行はOS、標準Cライブラリ、パッケージの配布形式、利用可能なシステムライブラリをまとめて変更しています。

そのため、単純な置き換えではなく、依存関係の構築プロセス全体を見直す変更だと考えてください。

Alpineを採用しやすいケース

Alpineが適しているのは、依存関係と実行方式を自分で管理できるプロジェクトです。

たとえば、次のような条件なら検討しやすくなります。

  • 実行時に必要なライブラリが少ない
  • 利用する依存パッケージがAlpine向けに問題なく提供されている
  • ネイティブ拡張のビルド手順を把握している
  • 最終イメージに含めるファイルを厳密に管理したい
  • ビルドと実行の環境をマルチステージビルドで分離できる
  • CI環境でビルド時間を継続的に確認できる

逆に、アプリケーションの依存関係が多く、複数の開発者が同じ環境を使い、あとから参加した人も短時間で構築できる状態を重視するなら、Alpineの小ささだけを理由に選ぶべきではありません。

Slimは「ほどよく小さく、Debianの仕組みを残す」選択

Slimバリアントの強みは、標準イメージから不要なものを削りながら、Debian系の互換性と運用感を保っていることです。

たとえばPythonでは、通常のpython:3.12.2が約570個以上のパッケージを含む一方、python:3.12.2-slim-bookwormは約150個のパッケージに抑えられています。もちろん、実際のイメージサイズはタグ、アーキテクチャ、含まれるファイルによって変わりますが、Slimが標準版の約50〜70%程度まで軽量化される目安は、選択時の比較材料になります。

ここで大切なのは、Slimが「何も入っていないイメージ」ではないことです。Debianをベースにしているため、Alpineよりも多くの基盤が残っています。その分だけ容量は大きくなりますが、glibcを前提にしたバイナリや、Debian系で検証されたパッケージを利用しやすいという利点があります。

Python環境では、この差が特に表れます。PyPIで配布されているホイールが利用できれば、パッケージのインストール時に毎回ソースコードをコンパイルする必要はありません。しかしAlpineでは、musl環境に対応したホイールが用意されていないライブラリの場合、ソースからのビルドへ切り替わることがあります。

NumPyやSciPyのような処理系では、これがビルド時間や失敗率に影響します。コンパイラ、Fortran関連の環境、数学ライブラリ、ヘッダーファイルなどが必要になり、単にPythonパッケージを追加するだけでは済まないこともあります。

Slimであれば、glibc環境向けのホイールを利用できる可能性が高く、依存パッケージのインストールが比較的素直に進みます。すべてのライブラリで問題が起きないわけではありませんが、少なくとも「OSを変えたことで、突然ソースビルドの仕組みを設計し直す」という事態は抑えやすくなります。

Slimでも不要なものは残る

Slimを選んだからといって、Dockerfileの改善が不要になるわけではありません。

apt-get updateapt-get installを使う場合は、パッケージ一覧の更新とインストールを同じRUN命令で行い、最後に/var/lib/apt/lists/*を削除する構成が一般的です。これは、パッケージをインストールしたあとに残る一覧ファイルを、実行時イメージへ持ち込まないためです。

ただし、ここでコマンドの形だけを覚えるのではなく、なぜ同じレイヤー内で削除するのかを理解しておきましょう。Dockerイメージはレイヤーを積み重ねて作られます。あるレイヤーでファイルを作成し、次のレイヤーで削除しても、下のレイヤーに記録されたデータまで消えるわけではありません。

つまり、次のように別々の命令へ分けると、見た目ではファイルを削除していても、イメージの履歴に容量が残ることがあります。

  • あるレイヤーでパッケージ一覧を取得する
  • 別のレイヤーでパッケージをインストールする
  • さらに別のレイヤーで一覧ファイルを削除する

実際のDockerfileでは、パッケージの更新、インストール、キャッシュ削除を一つの処理として組み立てるほうが、意図した軽量化につながります。

一方で、開発用ツールまで無理に削除してしまうと、あとでログ調査やデバッグが難しくなります。実行環境とビルド環境を分けることができるなら、マルチステージビルドで役割を切り分けるほうが、削除コマンドを増やすより再現性を保ちやすいでしょう。

PythonでAlpineを選ぶときに起きやすいこと

PythonのDocker環境では、AlpineとSlimの比較がよく話題になります。理由は、Pythonパッケージの中にCやFortranなどで実装された部分を持つものがあり、OSの標準Cライブラリの違いがビルドへ直接影響するからです。

たとえば、純粋なPythonコードだけで構成されたライブラリなら、Alpineへ移行しても大きな問題にならないことがあります。しかし、データ処理、画像処理、暗号処理、データベース接続などのライブラリでは、ネイティブ拡張が含まれている場合があります。

このとき、Slimでは対応するホイールをダウンロードしてインストールできる処理が、Alpineではソースコードのコンパイルに変わる可能性があります。

ビルドプロセスは、おおむね次のような構成になります。

1. Alpine向けのパッケージをapkで追加する

2. コンパイラや開発用ヘッダーを導入する

3. Pythonパッケージのソースコードをコンパイルする

4. ビルド後に不要なツールを削除する

5. 実行時に必要な共有ライブラリだけを残す

この流れを正しく設計できれば、Alpineでも動作するアプリケーションは作れます。しかし、Slimのようにホイールを利用して短時間で終わる構成と比べると、ビルド時間が大きく伸びることがあります。

さらに、ローカルでは成功したのにCIで失敗するケースにも注意してください。開発者の手元にはコンパイラやキャッシュが存在していても、CIではクリーンな環境から毎回ビルドすることがあります。そうすると、普段は見えていなかったビルド依存関係の不足が表面化します。

ここで確認したいのは、単にdocker buildが成功するかではありません。次の条件を同じように再現できるかを見ていきましょう。

  • ローカルとCIで同じベースイメージを使っているか
  • PythonやNode.jsなどのバージョンを固定しているか
  • 依存パッケージのバージョンを固定しているか
  • ネイティブ拡張のビルドに必要なパッケージが明示されているか
  • ビルドキャッシュがなくても完了するか
  • 実行時ステージに不要なコンパイラを持ち込んでいないか

再現性を重視するなら、アプリケーションの性質に対してAlpineが本当に必要なのかを先に考えることをおすすめします。イメージを数十MB削減できても、ビルドの失敗原因を調べる時間が増え、開発者ごとに異なる回避策が必要になるなら、プロジェクト全体では軽くなっていないからです。

Alpineのデメリットは、動かないことよりも、動かすための前提条件が増えることにあります。

apkとaptの違いは、コマンド名だけではない

AlpineとSlimを比較するとき、パッケージマネージャーの違いも見逃せません。

Alpineではapk、Debian系のSlimではaptまたはapt-getを使います。コマンドを置き換えれば終わるように見えますが、利用できるパッケージ名やパッケージの分割、設定ファイルの配置、依存関係の解決方法が異なる場合があります。

たとえば、Debianでbuild-essentialにまとまっているようなビルド環境が、Alpineではbuild-baseや個別の開発パッケージとして構成されることがあります。同じ目的のツールを導入していても、パッケージ名や提供されるファイルが一致するとは限りません。

また、シェルにも差があります。Alpineでは標準シェルとしてBusyBoxのashが使われる構成が一般的です。Debian系で普段使っているシェルスクリプトが、そのまま同じ挙動をするとは限りません。複雑なシェル処理、配列、特定シェル固有の構文を利用している場合は、スクリプトの実行環境を明示したほうが安全です。

パッケージ管理で起きやすい失敗を整理すると、次のようになります。

  • Debian向けのパッケージ名をAlpineでそのまま指定する
  • Alpine向けのパッケージが存在しないのに、似た名前だけで探し続ける
  • ビルド時に必要なパッケージと実行時に必要なパッケージを区別しない
  • パッケージ一覧を更新せずにインストールし、古い情報や取得エラーに遭遇する
  • キャッシュ削除の位置を誤り、イメージサイズが期待したほど減らない
  • シェルの違いを確認せず、起動スクリプトで構文エラーを起こす

このあたりは、Alpineが難しいというより、ベースOSの選択を変更したのに、Dockerfileの前提を変更していないことが原因です。OSを変えるなら、パッケージ名、シェル、証明書、ユーザー管理、ログ出力まで一度確認してみましょう。

Dockerfileでは「最終イメージ」と「ビルド工程」を分ける

AlpineとSlimのどちらを選んでも、軽量化を考えるうえで有効なのがマルチステージビルドです。

マルチステージビルドでは、コンパイルや依存関係の構築に必要なツールをビルド用ステージへ集め、最終ステージにはアプリケーションの実行に必要なファイルだけをコピーします。これにより、最終イメージへコンパイラや開発用ヘッダー、パッケージマネージャーのキャッシュを持ち込まずに済みます。

たとえば、Alpineでネイティブ拡張をビルドする場合、ビルド用ステージにはapk addでコンパイラや開発パッケージを追加し、最終ステージでは実行時ライブラリだけを残す構成が考えられます。Slimでも同じ考え方で、apt-get installによって導入したビルドツールを最終イメージから分離できます。

ただし、ステージを分ければ自動的に正しい構成になるわけではありません。ビルド環境と実行環境で標準Cライブラリが異なると、ビルドしたバイナリをコピーしても実行できないことがあります。

Alpineのビルドステージで作った成果物を、Debian系のSlimへコピーする場合や、その逆の場合は特に注意が必要です。コピーするファイルだけでなく、リンクしている共有ライブラリ、実行ユーザー、設定ファイルの配置まで確認しなければなりません。

マルチステージビルドで確認しやすい項目は、次の通りです。

  • ビルドステージと実行ステージのOS・標準Cライブラリが一致しているか
  • コンパイル済みバイナリが必要とする共有ライブラリを確認しているか
  • 実行時にだけ必要なパッケージを最終ステージへ追加しているか
  • 開発用ツールやキャッシュが最終ステージへ紛れ込んでいないか
  • コピーした成果物の所有者と実行権限が適切か
  • コンテナ起動時に必要な環境変数が欠けていないか

コンテナイメージの軽量化では、削除コマンドを増やすより、最初から最終イメージへ入れない設計にするほうが、仕組みとして明快です。

Dockerイメージ軽量化の目的を先に決める

AlpineかSlimかを決める前に、なぜ軽量化したいのかを言葉にしてみましょう。

目的によって、選ぶべき方向は変わります。コンテナレジストリへの転送時間を短くしたいのか、デプロイ先のディスク使用量を減らしたいのか、攻撃対象になりうるパッケージを削減したいのか、CIのビルド時間を短縮したいのかで、効果の出る対策が異なるからです。

たとえば、CIで何度もビルドするプロジェクトなら、ベースイメージの容量だけでなく、依存関係のインストール時間やキャッシュの効き方が重要になります。Alpineへ変更したことでベース部分が小さくなっても、毎回のソースビルドが発生すれば、CI全体では遅くなる可能性があります。

反対に、実行時イメージを小さくし、不要なツールを減らしたい場合は、Slimを使いながらマルチステージビルドや不要パッケージの削除を組み合わせるだけでも、十分な効果が得られることがあります。

目的ごとの考え方を整理すると、次のようになります。

1. ビルド時間を短くしたい場合

まず利用しているライブラリがバイナリパッケージを提供しているか確認します。Pythonでは、Alpineへ移行する前に、依存関係がソースビルドへ切り替わらないかを確認してみましょう。

2. 最終イメージを小さくしたい場合

ビルドツールを最終ステージから分離し、アプリケーションの実行に必要なファイルだけをコピーします。ベースイメージの変更は、そのあとに検討しても遅くありません。

3. デプロイの転送量を減らしたい場合

イメージのレイヤー構成を確認し、大きな依存ファイルやキャッシュがどのレイヤーに入っているかを調べます。不要なファイルを後段で削除するだけでは、転送量が減らないことがあります。

4. 脆弱性の対象を減らしたい場合

AlpineかSlimかだけで安全性を決めつけず、実際に含まれるパッケージを確認します。Slimでも不要なパッケージを削れば、攻撃対象になりうる範囲を大きく減らせます。

5. 開発チームの再現性を高めたい場合

すでに使っているOS系統と、依存ライブラリの配布形式を優先します。全員が同じ手順でビルドでき、エラーの原因を追いやすいことは、長期的な運用で大きな価値になります。

このように、軽量化の目的を分けると、Alpineの採用が本当に必要なのか見えやすくなります。

LaravelやWordPressの周辺環境ではどう考えるか

Laravelの開発環境では、PHP本体だけでなく、Composer、Node.js、データベース、キャッシュサーバー、画像処理ライブラリなど、複数のコンテナや依存関係が組み合わさることがあります。

PHPの拡張機能を追加する場合、ベースイメージによってパッケージ名やビルド手順が変わります。画像処理、データベース接続、国際化などの拡張を使う構成では、Alpineへ変更したあとに開発用パッケージの不足が見つかりやすくなります。

特に、LaravelアプリケーションのDockerfileを複数人で共有する場合は、最小サイズよりも、依存関係の説明がしやすいことを優先したほうが保守しやすいでしょう。SlimやDebian系の構成であれば、既存の解決策を参照しやすく、エラーが起きたときに調査の入口を作りやすいという利点があります。

WordPressでも、PHP、ウェブサーバー、データベース、画像処理、テーマやプラグインのビルド環境が関係します。実行環境だけを見るとAlpineの小ささは魅力的ですが、プラグインや画像処理ライブラリとの相性を含めると、Slimを使ったほうが構成を説明しやすいケースもあります。

もちろん、Alpineが不適切という意味ではありません。必要な拡張とパッケージを固定し、ビルド手順を検証し、実行時に必要なライブラリを切り分けられるなら、Alpineを使う理由は十分にあります。

ただ、個人開発ではインフラの作業を一人で抱えることが多く、数十MBの差より、数時間後に自分で原因を追える構成かどうかのほうが重要になる場面があります。夜にエラーを調べることになったとき、パッケージの前提が少ない環境は、それだけで助けになります。

AlpineとSlimを実際に選ぶための判断軸

最後に、プロジェクトへ導入する際の判断をもう少し具体化してみましょう。

まず、依存ライブラリにネイティブ拡張があるかを確認します。PythonならNumPyやSciPyのようなライブラリ、PHPなら追加拡張、Node.jsならネイティブアドオンなどが対象になります。これらが多い場合は、Slimから試すほうがビルドの流れを崩しにくいでしょう。

次に、ベースイメージのタグを固定します。latestのような動くタグではなく、利用する言語のバージョンとOSの世代まで指定したほうが、環境の変化を追いやすくなります。Slimの場合も、単にslimとするのではなく、Debianのリリースを含むタグを選ぶと、更新による差分を把握しやすくなります。

さらに、開発環境と本番環境で同じ前提を共有できるかを見ていきます。開発ではSlim、本番だけAlpineという分け方は、最終イメージを小さくできる一方、OSと標準Cライブラリが変わるため、実行時の差異が生まれます。ビルド成果物を共有するなら、開発・ビルド・実行の環境を無理なく一致させられる構成のほうが安全です。

判断に迷った場合は、次の順番で比較してみてください。

  • 現在のイメージサイズと、アプリケーション依存関係のサイズを分けて確認する
  • ベースイメージを変えたときのビルド時間を、キャッシュなしでも測る
  • 依存パッケージがバイナリ配布されるかを確認する
  • 開発用と実行用のパッケージを一覧にする
  • CIでクリーンビルドし、ローカルとの差を確認する
  • コンテナ起動後のヘルスチェックや主要処理まで動作確認する
  • エラー発生時にログやシェルを使って調査できるかを確認する

ここまで確認して、Alpineの小ささが明確な価値になるなら採用してよいでしょう。逆に、Slimでも十分に軽量で、依存関係の導入やトラブルシューティングが安定するなら、Slimを選ぶことは妥協ではありません。

まとめ:最初の一手はSlim、理由があるならAlpine

DockerイメージのAlpineとSlimの違いは、ベースイメージの容量だけではありません。

AlpineはAlpine Linuxとmusl libcによって非常に小さな基盤を提供します。その一方で、glibc前提のバイナリや、musl向けホイールが用意されていないライブラリでは、ソースビルドや互換性の問題が起きる可能性があります。

SlimはDebianをベースに不要なパッケージを削減した構成で、Alpineほど極端に小さくはありません。しかし、glibcとの互換性、aptによるパッケージ管理、既存ライブラリの利用しやすさを保ちながら、標準イメージより軽量化できます。

私が個人開発の環境でまず試すなら、依存関係が多いアプリケーションではSlimを起点にします。そこからイメージサイズや転送時間に具体的な課題があり、かつAlpine向けのビルド手順を再現性のある形で管理できると判断した段階で、Alpineへ進む流れです。

Dockerfileの1行を変更するだけで済むように見えても、実際にはOS、ライブラリ、ビルドプロセス、運用時の調査方法まで変わります。あなたのプロジェクトで必要なのが「極小サイズ」なのか、それとも「長く安定して扱える軽量環境」なのかを切り分けてから、まずはSlimとAlpineを同じ依存関係でビルドして比べてみましょう。数字だけでなく、ビルド時間とエラーの再現性まで確認すると、無理のない選択が見えてきます。

Related reading: Docker・インフラ構築をわかりやすく解説 and Laravelの本番環境向けDockerfile:マルチステージビルドによるイメージ軽量化の是非.

よくある質問

AlpineとSlimのどちらを選ぶべきですか?
依存関係が多くネイティブ拡張を含む場合はSlimが適しています。極小サイズが必須で、かつビルド手順や依存関係を完全に管理できる場合はAlpineを検討してください。
Alpineでglibcが必要なライブラリは使えますか?
Alpineはmusl libcを採用しているため、glibc向けにビルドされたバイナリはそのままでは動作しません。互換レイヤーの導入やソースからのビルドが必要になる場合があります。
Dockerfileでイメージを軽量化するコツはありますか?
マルチステージビルドを使用して、ビルドに必要なツールと実行に必要なファイルを分離してください。また、パッケージのインストールとキャッシュ削除を同じRUN命令内で行うことも重要です。
Alpineに変更したらビルドが遅くなったのはなぜですか?
Alpine向けに提供されているバイナリパッケージがない場合、インストール時にソースコードからコンパイルが行われるため、ビルド時間が大幅に伸びることがあります。

参考情報