Docker 上級 #1 BuildKit と buildx — ビルダーの正体
中級シリーズ では BuildKit を「デフォルトで有効になっている何か」程度に扱いました。上級シリーズはその中を一度覗いていきます。この記事はビルダー自体の構造、そして BuildKit の上に乗ったツール buildx の役割を整理します。
Docker 上級 シリーズでこの記事の位置:
- #1 BuildKit と buildx ← この記事
- #2 マルチアーキテクチャイメージ (linux/amd64 + arm64)
- #3 イメージセキュリティ — non-root, distroless, scan(Trivy)
- #4 SBOM と署名 (cosign)
- #5 リソース制限と cgroups
- #6 プロダクション運用 — restart 方針、healthcheck、graceful shutdown
二つのビルダーの短い歴史 #
Docker のビルドエンジンが二世代に分かれているのは 中級 #2 で触れました。一段だけ解きほぐします。
Legacy builder は docker build が元々持っていたビルダーです。単純なモデル — Dockerfile を上から下へ一行ずつ実行し、各行ごとに一時コンテナを作って commit する方式。直感的ですが限界も明確でした。並列処理がなく、キャッシュモデルが単純で、イメージに秘密が刻まれるリスクが大きかった。
BuildKit は Docker 社の外で始まり (Moby プロジェクト)、後に Docker に統合された次世代ビルダーです。核心アイデアは二つ:
- Dockerfile をグラフ (LLB) にコンパイル — 依存のないノードは並列実行
- frontend / backend の分離 — Dockerfile 以外の形式も入力にできる拡張可能な構造
Dockerfile bake.hcl 他の frontend
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────┐
│ Frontend (Dockerfile parser, etc.) │
└────────────────┬─────────────────────────┘
│ produces
▼
┌──────────────────────────────────────────┐
│ LLB — Low-Level Build graph │
│ (依存グラフ、キャッシュキー、並列可能) │
└────────────────┬─────────────────────────┘
│ executed by
▼
┌──────────────────────────────────────────┐
│ BuildKit backend │
│ (snapshotter、cache、executor) │
└────────────────┬─────────────────────────┘
│ outputs
▼
イメージ / tar / ローカルディレクトリ / 等LLB はユーザーが直接触ることはほとんどないですが、「この行とその行が本当に同時に実行されるのだな」のような BuildKit の動作を理解するときに頭に置いておくと良いモデルです。
# syntax= 一行の正体
#
中級 #2 で触れたこの行。
# syntax=docker/dockerfile:1.7
FROM ...これが frontend 指定です。Dockerfile parser 自体のどのバージョンを使うか を指します。BuildKit はこの一行を見てその frontend を OCI イメージとして取得しコンパイルに使います。新しい Dockerfile 機能 (RUN --mount、COPY --link、--exclude など) が追加されるたびに frontend バージョンが上がります。
信頼できるピン:
docker/dockerfile:1— 1.x の latest。よく使うデフォルトdocker/dockerfile:1.7— 1.7 シリーズにピンdocker/dockerfile:1.7.0— 正確に一バージョンに
運用 / 再現性が大事な場面ではマイナーバージョンまでピンする方が安全です。
buildx — BuildKit の上の CLI
#
docker build と docker buildx build の違いを整理すると:
docker build | docker buildx build | |
|---|---|---|
| ビルダー | デーモン内蔵 (BuildKit または legacy) | 外部 builder インスタンス (BuildKit) |
| マルチプラットフォーム | 不可 | --platform linux/amd64,linux/arm64 可 |
| キャッシュインポート / エクスポート | 限定的 | type=registry、type=gha など自由 |
--output | イメージのみ | tar、oci、local、registry など多様 |
| 並列ビルダー | 単一 | 複数インスタンス可 |
要約すると buildx は BuildKit のフル機能にアクセスする CLI です。最近の Docker は二つのコマンドをほぼ同等に束ねる流れで、docker build が内部的に buildx を呼ぶ形になっています。新しいコードはそのまま buildx で書く方がすっきりします。
Builder インスタンス — ビルドをどこで回すか #
buildx の核心概念は builder インスタンス です。ビルドを実際に回す BuildKit デーモンをどこに置くかを選べます。
docker buildx ls
# NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
# default * docker
# \_ default \_ unix:///var/run/... running linux/amd64, linux/arm64デフォルトは default という名前の docker ドライバです。ビルドは Docker デーモン自身が持つ BuildKit の上で回ります。
Driver の種類 #
| Driver | どこで動く | マルチプラットフォーム |
|---|---|---|
docker (デフォルト) | Docker デーモン内蔵 BuildKit | ホストのアーキだけ |
docker-container | 別途コンテナとして立ち上げた BuildKit | 可 (QEMU emulation) |
kubernetes | k8s クラスターの pod | 可 |
remote | リモート BuildKit デーモン | 環境による |
docker ドライバの最大の限界はマルチプラットフォームビルドができない点 です。だから docker-container ドライバで新しいビルダーを作るのがほぼ最初のステップです。
新しい builder を作る #
docker buildx create --name multi --driver docker-container --use
docker buildx ls
# NAME/NODE DRIVER/ENDPOINT STATUS PLATFORMS
# multi * docker-container
# \_ multi0 \_ unix:///var/run/... inactive
# default docker
# \_ default \_ unix:///var/run/... running linux/amd64, linux/arm64--name multi— ビルダー名--driver docker-container— 別コンテナの BuildKit--use— 作って即アクティブビルダーに指定
inactive 状態なのはまだ初回ビルドを回していないという意味。最初のビルドを回せば BuildKit コンテナ (moby/buildkit:...) が自動で立ち上がります。
docker buildx inspect multi --bootstrap
# 即座に BuildKit コンテナを立ち上げるビルダーの切り替え / 削除:
docker buildx use default # デフォルトビルダーへ
docker buildx use multi # multi ビルダーへ
docker buildx rm multi # 削除
docker buildx prune --all # 全ビルダーのキャッシュを整理--output — ビルド結果の出力先
#
buildx の表現力が一番現れるところです。ビルド結果をイメージとしてだけでなく様々な形で取り出せます。
# 1) Docker イメージとして (load) — ローカルイメージキャッシュに入る
docker buildx build --output type=docker -t myapp .
# または (短縮)
docker buildx build --load -t myapp .
# 2) レジストリへ直接 push
docker buildx build --output type=registry -t ghcr.io/me/myapp:1.0 .
# または (短縮)
docker buildx build --push -t ghcr.io/me/myapp:1.0 .
# 3) tar ファイルとして
docker buildx build --output type=tar,dest=myapp.tar .
# 4) OCI イメージ layout として (ディレクトリ)
docker buildx build --output type=oci,dest=myapp-oci/ .
# 5) ビルド結果のファイルシステムをローカルに抽出 (イメージは作らない)
docker buildx build --output type=local,dest=./out --target builder .type=local は面白い出力先です。イメージを作らずに ビルド段階の結果ファイルだけ抽出します。CI がビルド成果物 (例: コンパイルされたバイナリ) を Docker と無関係に次の段階に渡したいときに有用です。
docker buildx build --target builder --output type=local,dest=./bin .
ls bin/
# myappマルチプラットフォームビルド — 短い味見 #
docker-container ドライバの上で一つのコマンドで二つのアーキテクチャを同時にビルドできます。
docker buildx build \
--platform linux/amd64,linux/arm64 \
-t ghcr.io/me/myapp:1.0 \
--push .このとき作られるのは二つのアーキテクチャイメージを束ねた manifest list (OCI image index) です。ユーザーは自分のアーキテクチャに合うイメージを自動で受け取ります。深掘りは #2 で本格的に扱います。
--load と --platform の衝突一つ: Docker デーモンのローカルイメージキャッシュは single-platform だけサポートするので、複数プラットフォームを一度に --load するのはできません。 --push するか、一プラットフォームだけ選んで --load。
キャッシュエクスポート — --cache-to/from 深掘り
#
中級 #2 で触れた外部キャッシュ。オプションがもう少しあります。
docker buildx build \
--cache-to=type=registry,ref=ghcr.io/me/myapp:cache,mode=max,compression=zstd \
--cache-from=type=registry,ref=ghcr.io/me/myapp:cache \
--push -t ghcr.io/me/myapp:1.0 .| オプション | 意味 |
|---|---|
mode=min (デフォルト) | 結果レイヤーだけキャッシュ |
mode=max | 全中間ステージキャッシュ (マルチステージで効果大) |
compression=gzip|zstd|estargz | キャッシュレイヤー圧縮。zstd が速くて小さい |
image-manifest=true | OCI 標準 manifest 形式で (互換性↑) |
oci-mediatypes=true | OCI media type 強制 |
マルチステージが深ければ mode=max が大きな差を作ります。 ビルダー / 依存 / テストステージのキャッシュまで全て生きていて、小さな変更でもほぼ即座にビルドが終わります。短所はキャッシュ自体が大きくなること。
docker buildx bake — 宣言的多段ビルド
#
複数のイメージを一度にビルドする必要があるとき (monorepo: web + worker + admin)、シェルスクリプトで buildx build を何度も呼ぶのはすぐに散らかります。bake がそれを宣言的に解いてくれます。
group "default" {
targets = ["web", "worker", "admin"]
}
target "web" {
context = "./web"
tags = ["ghcr.io/me/web:1.0", "ghcr.io/me/web:latest"]
platforms = ["linux/amd64", "linux/arm64"]
cache-from = ["type=registry,ref=ghcr.io/me/web:cache"]
cache-to = ["type=registry,ref=ghcr.io/me/web:cache,mode=max"]
}
target "worker" {
context = "./worker"
tags = ["ghcr.io/me/worker:1.0"]
platforms = ["linux/amd64", "linux/arm64"]
}
target "admin" {
inherits = ["web"]
context = "./admin"
tags = ["ghcr.io/me/admin:1.0"]
}docker buildx bake --push
# 三つのイメージを同時に、マルチプラットフォームで、キャッシュまで束ねてbake の強み:
- 並列ビルド —
web、worker、adminがお互い依存なしなら同時ビルド inheritsで共通設定を継承- 変数 / 関数 — HCL の
variable、functionで環境ごとに分岐 - JSON / HCL / compose ファイル — 三つとも入力可能 (compose.yaml の
build:定義もそのまま bake の入力)
docker buildx bake -f compose.yaml --push同じ定義を二度書かず — compose が定義したビルドをそのまま CI で回すパターンです。
bake のよくあるパターン — 環境ごとの分岐 #
variable "TAG" { default = "dev" }
variable "REGISTRY" { default = "ghcr.io/me" }
target "_common" {
platforms = ["linux/amd64", "linux/arm64"]
}
target "web" {
inherits = ["_common"]
context = "./web"
tags = ["${REGISTRY}/web:${TAG}"]
}docker buildx bake web # dev タグ
TAG=1.2.3 REGISTRY=ghcr.io/me docker buildx bake web --push # 運用CI のビルドスクリプトがとても軽くなります。
その他のよく使う buildx オプション #
--progress=plain # フルログ (BuildKit の一行サマリーの代わり)
--progress=quiet # 最小出力
--no-cache-filter=test # 特定ステージだけキャッシュ無視
--build-context other=./other-dir # 追加ビルドコンテキスト命名
--allow=network.host # ビルドの中でホストネットワーク使用 (セキュリティ意識)--build-context は monorepo で別ディレクトリをコンテキストとして持ってくるときに有用です。Dockerfile で COPY --from=other ./shared/types /app/types のように受けます。
まとめ #
この記事で掴んだ絵:
- BuildKit = LLB (グラフ) + frontend (Dockerfile parser) + backend。
# syntax=が frontend ピン buildxは BuildKit のフル機能 CLI。新しいコードは buildx で- builder インスタンス が核心概念 —
dockerドライバはマルチプラットフォーム不可、docker-containerドライバ で新しいビルダーを作るのがほぼ最初のステップ --outputで結果をイメージ / tar / OCI / ローカルディレクトリ / レジストリに自由に- 外部キャッシュは
mode=max+compression=zstdがマルチステージに効果的 docker buildx bakeで monorepo / 多段ビルドを宣言的に。compose.yaml 入力も可
次の記事 (#2 マルチアーキテクチャイメージ) では --platform 一オプションの裏にある仕組み — manifest list、QEMU emulation、ネイティブマルチアーキビルダー、そして Apple Silicon でビルドしたイメージが運用サーバで動かないよくある事故をどう避けるかを深く見ます。