Docker 上級 #1 BuildKit と buildx — ビルダーの正体

読了 8分

中級シリーズ では 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 builderdocker build が元々持っていたビルダーです。単純なモデル — Dockerfile を上から下へ一行ずつ実行し、各行ごとに一時コンテナを作って commit する方式。直感的ですが限界も明確でした。並列処理がなく、キャッシュモデルが単純で、イメージに秘密が刻まれるリスクが大きかった。

BuildKit は Docker 社の外で始まり (Moby プロジェクト)、後に Docker に統合された次世代ビルダーです。核心アイデアは二つ:

  1. Dockerfile をグラフ (LLB) にコンパイル — 依存のないノードは並列実行
  2. frontend / backend の分離 — Dockerfile 以外の形式も入力にできる拡張可能な構造
BuildKit のビルドフロー
   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 で触れたこの行。

frontend 明示
# syntax=docker/dockerfile:1.7
FROM ...

これが frontend 指定です。Dockerfile parser 自体のどのバージョンを使うか を指します。BuildKit はこの一行を見てその frontend を OCI イメージとして取得しコンパイルに使います。新しい Dockerfile 機能 (RUN --mountCOPY --link--exclude など) が追加されるたびに frontend バージョンが上がります。

信頼できるピン:

  • docker/dockerfile:1 — 1.x の latest。よく使うデフォルト
  • docker/dockerfile:1.7 — 1.7 シリーズにピン
  • docker/dockerfile:1.7.0 — 正確に一バージョンに

運用 / 再現性が大事な場面ではマイナーバージョンまでピンする方が安全です。

buildx — BuildKit の上の CLI #

docker builddocker buildx build の違いを整理すると:

docker builddocker buildx build
ビルダーデーモン内蔵 (BuildKit または legacy)外部 builder インスタンス (BuildKit)
マルチプラットフォーム不可--platform linux/amd64,linux/arm64
キャッシュインポート / エクスポート限定的type=registrytype=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)
kubernetesk8s クラスターの pod
remoteリモート BuildKit デーモン環境による

docker ドライバの最大の限界はマルチプラットフォームビルドができない点 です。だから docker-container ドライバで新しいビルダーを作るのがほぼ最初のステップです。

新しい builder を作る #

docker-container ドライバビルダー
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 と無関係に次の段階に渡したいときに有用です。

Go バイナリだけ抽出
docker buildx build --target builder --output type=local,dest=./bin .
ls bin/
# myapp

マルチプラットフォームビルド — 短い味見 #

docker-container ドライバの上で一つのコマンドで二つのアーキテクチャを同時にビルドできます。

amd64 + arm64 同時
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=trueOCI 標準 manifest 形式で (互換性↑)
oci-mediatypes=trueOCI media type 強制

マルチステージが深ければ mode=max が大きな差を作ります。 ビルダー / 依存 / テストステージのキャッシュまで全て生きていて、小さな変更でもほぼ即座にビルドが終わります。短所はキャッシュ自体が大きくなること。

docker buildx bake — 宣言的多段ビルド #

複数のイメージを一度にビルドする必要があるとき (monorepo: web + worker + admin)、シェルスクリプトで buildx build を何度も呼ぶのはすぐに散らかります。bake がそれを宣言的に解いてくれます。

docker-bake.hcl
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 の強み:

  • 並列ビルドwebworkeradmin がお互い依存なしなら同時ビルド
  • inherits で共通設定を継承
  • 変数 / 関数 — HCL の variablefunction で環境ごとに分岐
  • JSON / HCL / compose ファイル — 三つとも入力可能 (compose.yaml の build: 定義もそのまま bake の入力)
compose.yaml を bake 入力として
docker buildx bake -f compose.yaml --push

同じ定義を二度書かず — compose が定義したビルドをそのまま CI で回すパターンです。

bake のよくあるパターン — 環境ごとの分岐 #

dev / prod 分岐
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 でビルドしたイメージが運用サーバで動かないよくある事故をどう避けるかを深く見ます。

X