🐧 Linux 総合学習プラットフォーム
コンテナ Docker/Podman ・ 中級

Dockerfileの実践——軽く・速く

Dockerfileはただ書けば動くというものではありません。書き方ひとつでビルド時間もイメージの容量も大きく変わります。ここではレイヤとキャッシュの仕組みを理解したうえで、変わりにくい命令を上に置く順序の工夫、.dockerignoreによる無駄の排除、軽量ベースイメージの選び方、そしてビルド道具を最終イメージに残さないmulti-stage buildまでを学びます。同じアプリでも、書き方次第でイメージは数百MBから数十MBまで軽くなります。

Dockerfileは、書けば動く。だが同じアプリを作るにしても、書き方ひとつでビルド時間もイメージの容量も何倍も変わってくる。

よくある失敗は、とりあえず動いたDockerfileをそのまま使い続けることだ。ビルドのたびに5分待たされる、イメージが1GBを超えている、そんな状態でも「動いているから」と放置してしまう。

つまずき動くDockerfileと、良いDockerfileは別物だ。良いDockerfileは速く・軽く・安全に同じ結果を再現する。

🧱 レイヤとキャッシュの仕組み

Dockerfileの各命令(FROM・RUN・COPYなど)は、実行されるたびに1枚のレイヤとして積み重なる。イメージの正体は、このレイヤを重ねた層構造だ。

ビルドを再実行するとき、Dockerは上から順に命令を見ていき、前回と内容が変わっていないレイヤはキャッシュを再利用してスキップする。だが、どこか1つの行が変わると、その行から下のレイヤは全部作り直しになる。

🔗
たとえレイヤは重ねたホットケーキだ。上のほうの生地を変えても下の生地はそのままでいい。だが下のほうの生地を変えたら、その上に乗っている全部を焼き直すしかない。
FROM slimCOPY requirementsRUN pip installCOPY app/3行はキャッシュ再利用される変わらないので変わった行から下だけ作り直し
💡
ポイント上から下へ順に見て、変わった行のレイヤから下だけが作り直される。だから「変わりやすい行」ほど下に置くと、キャッシュの恩恵を最大化できる。

📋 変わりにくい行を上へ

この仕組みを踏まえると、Dockerfileを書く順番には正解がある。依存関係のインストールを先に済ませ、ソースコードのコピーはできるだけ後にする、という順序だ。

アプリのソースコードは毎日のように変わるが、依存ライブラリの一覧(requirements.txtやpackage.jsonなど)はそう頻繁には変わらない。だから依存インストールを先、ソースCOPYを後にしておけば、ソースだけ直した再ビルドでも依存インストールのレイヤはキャッシュから再利用される。

悪い例と良い例を比べてみる。悪い例はCOPY . .で全部を先にコピーしてからpip installする。これだとソースを1行変えるだけで依存インストールからやり直しになる。良い例はCOPY requirements.txt→RUN pip install→COPY . .の順にする。
コツ依存ファイルだけを先にCOPYし、ソース全体のCOPYは一番下に置く。この並び替えだけで日々の再ビルドは体感できるほど速くなる。

🗑️ .dockerignoreで無駄を削る

COPY . .のように書くと、Dockerfileがあるフォルダの中身をまるごとビルドの材料として送り込む。だが、その中には.gitフォルダやnode_modulesなど、イメージに含める必要のないものが紛れ込んでいることが多い。

.dockerignoreは、ビルドの材料から除外するファイルやフォルダを指定する設定ファイルだ。.gitignoreとよく似た書き方で、1行に1パターンを書く。

.dockerignoreの中身の例。node_modules・.git・*.log・.env のように列挙しておく。ビルドに送るデータ量が減って速くなるうえ、秘密情報を含めてしまう事故も防げる。
つまずき.envのような秘密情報が.dockerignoreで除外されていないと、そのままイメージの中に焼き込まれてしまう。除外設定は容量対策だけでなくセキュリティ対策でもある。

🪶 軽量ベースイメージの使い分け

FROM行で指定するベースイメージの選び方も、最終的な容量に直結する。同じPythonでも、pythonという素のタグを使うか、python:slimを使うか、python:alpineを使うかで容量は大きく変わる。

slimは、フルサイズのイメージから開発ツールやドキュメントなど普段使わないものを削った軽量版だ。alpineはさらに徹底していて、Alpine Linuxという非常に小さいディストリビューションをベースにしている。

ただしalpineには注意点がある。標準Cライブラリがglibcではなくmuslという別実装のため、一部のパッケージがそのままでは動かなかったりする。軽さだけを見て飛びつくと詰まることがある。

💡
ポイントslimは無難な軽量選択、alpineは最軽量だが互換性の落とし穴に注意。迷ったらまずslimで試し、それでも重ければalpineを検討する、という順番が安全だ。

🏗️ multi-stage build——道具は残さない

アプリによっては、ビルドの過程でコンパイラなど実行時には不要な道具が必要になる。これらをそのまま最終イメージに残すと、容量が膨らむだけでなく、不要なツールが攻撃の足がかりになる可能性も増える。

multi-stage buildは、この問題を解決する仕組みだ。Dockerfileの中に複数のFROMを書き、前段のステージでビルドだけを行い、最終ステージでは前段から必要な成果物だけをCOPY --from=でコピーする。ビルド道具そのものは最終イメージに一切残らない。

ビルド段階gcc / make→実行ファイル生成最終段階実行ファイルのみgccは含まれない軽量・安全な最終像
Go言語の例なら、1段目でFROM golang:1.22としてビルドし、2段目でFROM alpineとして1段目から実行ファイルだけをCOPY --from=0 /app/server /server のようにコピーする。最終イメージにはGoのコンパイラは含まれない。
コツFROMの後に AS builder のような名前を付けておくと、COPY --from=builder のように名前で参照できて読みやすい。

🔄 まとめて実践する

ここまでの4つの工夫、キャッシュを意識した命令順・.dockerignore・軽量ベース・multi-stage buildは、それぞれ単独でも効くが、組み合わせるとさらに効果が大きい。

実際に容量を確認したいときはdocker imagesでSIZE列を見比べるとよい。素のベースイメージで作った版とmulti-stage buildで整えた版とでは、数百MBから数十MBへと桁が変わることも珍しくない。

Dockerfileを整えたら、次は複数コンテナを組み合わせて動かす段階に進める。アプリとデータベースの組を1枚の設定ファイルで管理する方法を見ていこう。

この項目に出てくる用語

レイヤれいや
イメージを構成する、差分の積み重ねの一段。
Dockerfileどっかーふぁいる
イメージの作り方を順に書いたテキストファイル。
イメージいめーじ
アプリと実行環境を固めた読み取り専用の雛形。

関連コマンド

docker build

▶ 学習アプリでこの続きを学ぶ・演習する