ビルドが始まる前にもう遅くなっている
一言でいうと
docker build .の、その点1つが、現在のディレクトリ全体をビルダーに転送します。キャッシュをどれだけうまく組んでも、転送するものが800MBなら、ビルドはその前にすでに遅くなっています。
なぜ必要なのか
Dockerfileはたった2行なのに、ビルドに40秒かかります。ログの最初の行に答えがあります。
Sending build context to Docker daemon 812.4MB
ビルドが始まる前に、現在のディレクトリをまるごと圧縮して、ビルダーに送る段階です。node_modules、.git、dist、昨日ダウンロードしておいたダンプファイルまで、すべて送られます。Dockerfileで使っていなくても、転送はされます。
ここで、2つが同時に壊れます。
- 速度: ビルドのたびに数百MBを転送します。
- キャッシュ:
COPY . .は、コンテキスト内のファイルが1つでも変わると壊れます。.git/indexは、git statusを実行しただけでも変わります。そのため、ソースを直していないのにキャッシュが壊れることが起きます。
どう動くのか
.dockerignoreは、.gitignoreと文法が似ていますが、目的が違います。.gitignoreは「バージョン管理しないもの」、.dockerignoreは「ビルダーに送らないもの」です。重なる部分は多いものの、同じではありません。たとえば、.gitは.gitignoreに入れられませんが、.dockerignoreには必ず入れる必要があります。
.git
node_modules
**/__pycache__
*.log
dist/
.env
最後の行が重要です。.envをコンテキストに置くと、COPY . .でイメージにそのまま埋め込まれます。あとで削除するレイヤーを追加しても、前のレイヤーには残ります。
再現性: 同じDockerfileで結果が変わる
.dockerignoreでコンテキストを減らしても、ビルドが再現されない、よくある理由がさらに2つあります。
フローティングタグでは、FROM python:3.12は、今日と来月で別のイメージになります。FROM python:3.12.7-slimのように固定するか、さらに厳密には、ダイジェストでFROM python@sha256:...を使います。
パッケージインデックスでは、apt-get install curlは、その日の最新バージョンを取得します。昨日通ったビルドが今日壊れる典型的な原因で、本当に必要な場所では、バージョンを明示します。
| 問題 | 症状 | 対応 |
|---|---|---|
| 大きなコンテキスト | 「Sending build context」が数百MB | .dockerignore |
.gitを含む |
ソースを直していないのにキャッシュが壊れる | .dockerignoreに.git |
| フローティングタグ | 先週のビルドと結果が違う | パッチバージョン・ダイジェストで固定 |
| シークレットの混入 | イメージに.envが入る |
.dockerignore + ビルドシークレット |
イメージに残るARGの値
ARG API_TOKEN
RUN curl -H "Authorization: $API_TOKEN" ...
このように書くと、docker historyにその値が残ります。ビルド時の機密情報は、ARGではなくBuildKitのシークレットマウントで渡します。ARGは、「バージョン番号」のような、公開してもよい値にだけ使います。
現場での姿
- 新入社員が
git cloneしたあとの最初のビルドに5分かかります。.dockerignoreがありません。 - CIは速いのに、ローカルだけ遅いです。ローカルには
node_modulesとビルドのアーティファクトがあります。 - イメージから顧客のダンプCSVが見つかりました。コンテキストにあり、
COPY . .でした。
コンテキストを減らす以外に、送らずに済ませる方法
.dockerignoreは送るものを減らしますが、そもそも別の経路で持ち込む方法もあります。どちらが正しいかは、そのファイルがビルドに必要な理由で分かれます。
ビルドにだけ必要で、最終イメージにはあってはならないものについては、前のコースで見たマルチステージビルドとシークレットマウントの出番です。認証情報はコンテキストにそもそも入れず、マウントで渡し、コンパイルツールと中間のアーティファクトは前のステージに置いて、結果だけを移します。
ビルド中にダウンロードするものについては、キャッシュマウントを使うと、依存関係のディレクトリをコンテキストに入れなくても、ビルド間で再利用できます。node_modulesをまるごと送らなくてよい理由がこれで、コンテキストが小さくなると同時に、キャッシュも良くなります。
リモートから取得するものについては、ビルダーは、コンテキストの代わりにgitリポジトリのURLやtarballのURLを受け取ることもできます。CIのように、すでにその場所にソースがある環境では大きな違いはありませんが、コンテキストを作れない場所では、この方法が唯一の道です。
そして、実際に何が送られたかを確認する方法を知っておくと役に立ちます。コンテキストに何が入るかは、.dockerignoreルールの順序と否定パターンによって混乱しやすいですが、イメージを作ったあとにその中のファイル一覧を確認すれば、意図しないものが入っていないかがすぐにわかります。機密情報が入っていないかを確認する最も確実な方法もこれで、前のコースで見たとおり、レジストリに上がったあとは元に戻せないため、上げる前に一度確認することの価値が大きいです。
次の確認で見ること
続くクイズでは、ビルドコンテキストの境界、.dockerignore、キャッシュキーとシークレットの受け渡し方式を判断します。前のラボで測定した転送サイズとキャッシュ無効化の結果を根拠に、ソースの変更とは無関係なファイルがビルドに与える影響を、区別してみてください。