Dockerfileの実践——軽く・速く
Dockerfileはただ書けば動くというものではありません。書き方ひとつでビルド時間もイメージの容量も大きく変わります。ここではレイヤとキャッシュの仕組みを理解したうえで、変わりにくい命令を上に置く順序の工夫、.dockerignoreによる無駄の排除、軽量ベースイメージの選び方、そしてビルド道具を最終イメージに残さないmulti-stage buildまでを学びます。同じアプリでも、書き方次第でイメージは数百MBから数十MBまで軽くなります。
Dockerfileは、書けば動く。だが同じアプリを作るにしても、書き方ひとつでビルド時間もイメージの容量も何倍も変わってくる。
よくある失敗は、とりあえず動いたDockerfileをそのまま使い続けることだ。ビルドのたびに5分待たされる、イメージが1GBを超えている、そんな状態でも「動いているから」と放置してしまう。
🧱 レイヤとキャッシュの仕組み
Dockerfileの各命令(FROM・RUN・COPYなど)は、実行されるたびに1枚のレイヤとして積み重なる。イメージの正体は、このレイヤを重ねた層構造だ。
ビルドを再実行するとき、Dockerは上から順に命令を見ていき、前回と内容が変わっていないレイヤはキャッシュを再利用してスキップする。だが、どこか1つの行が変わると、その行から下のレイヤは全部作り直しになる。
📋 変わりにくい行を上へ
この仕組みを踏まえると、Dockerfileを書く順番には正解がある。依存関係のインストールを先に済ませ、ソースコードのコピーはできるだけ後にする、という順序だ。
アプリのソースコードは毎日のように変わるが、依存ライブラリの一覧(requirements.txtやpackage.jsonなど)はそう頻繁には変わらない。だから依存インストールを先、ソースCOPYを後にしておけば、ソースだけ直した再ビルドでも依存インストールのレイヤはキャッシュから再利用される。
🗑️ .dockerignoreで無駄を削る
COPY . .のように書くと、Dockerfileがあるフォルダの中身をまるごとビルドの材料として送り込む。だが、その中には.gitフォルダやnode_modulesなど、イメージに含める必要のないものが紛れ込んでいることが多い。
.dockerignoreは、ビルドの材料から除外するファイルやフォルダを指定する設定ファイルだ。.gitignoreとよく似た書き方で、1行に1パターンを書く。
🪶 軽量ベースイメージの使い分け
FROM行で指定するベースイメージの選び方も、最終的な容量に直結する。同じPythonでも、pythonという素のタグを使うか、python:slimを使うか、python:alpineを使うかで容量は大きく変わる。
slimは、フルサイズのイメージから開発ツールやドキュメントなど普段使わないものを削った軽量版だ。alpineはさらに徹底していて、Alpine Linuxという非常に小さいディストリビューションをベースにしている。
ただしalpineには注意点がある。標準Cライブラリがglibcではなくmuslという別実装のため、一部のパッケージがそのままでは動かなかったりする。軽さだけを見て飛びつくと詰まることがある。
🏗️ multi-stage build——道具は残さない
アプリによっては、ビルドの過程でコンパイラなど実行時には不要な道具が必要になる。これらをそのまま最終イメージに残すと、容量が膨らむだけでなく、不要なツールが攻撃の足がかりになる可能性も増える。
multi-stage buildは、この問題を解決する仕組みだ。Dockerfileの中に複数のFROMを書き、前段のステージでビルドだけを行い、最終ステージでは前段から必要な成果物だけをCOPY --from=でコピーする。ビルド道具そのものは最終イメージに一切残らない。
🔄 まとめて実践する
ここまでの4つの工夫、キャッシュを意識した命令順・.dockerignore・軽量ベース・multi-stage buildは、それぞれ単独でも効くが、組み合わせるとさらに効果が大きい。
実際に容量を確認したいときはdocker imagesでSIZE列を見比べるとよい。素のベースイメージで作った版とmulti-stage buildで整えた版とでは、数百MBから数十MBへと桁が変わることも珍しくない。
Dockerfileを整えたら、次は複数コンテナを組み合わせて動かす段階に進める。アプリとデータベースの組を1枚の設定ファイルで管理する方法を見ていこう。