Docker のビルドキャッシュの肥大化を自動削除設定で抑制する方法

Docker のビルドキャッシュの肥大化を自動削除設定で抑制する方法

Docker のビルドキャッシュが膨らむのは GC が動いていないからではなく、削除条件が想像より寛容だからです。daemon.json の設定がドキュメントと実装でずれている箇所が多く、本記事ではその 5 つを整理し、実際に効く設定をまとめました。
2026.08.20

はじめに

製造ビジネステクノロジー部のふじい(大)です。

Docker を使っていると、いつの間にかディスクの空きが減っていることがあります。
docker system df を見ると、だいたいビルドキャッシュが数 GB を占めています。

TYPE            TOTAL     ACTIVE    SIZE      RECLAIMABLE
Images          10        1         9.55GB    391.8MB (4%)
Containers      1         1         2.807GB   0B (0%)
Local Volumes   3         3         1.006GB   0B (0%)
Build Cache     53        0         3.681GB   513.6MB

気付いたときに docker builder prune を叩けば済む話ではあります。
ただ、逼迫してから慌てて消すという流れを繰り返すのも面倒です。

BuildKit にはキャッシュを自動削除する仕組み(ガベージコレクション、GC)があり、daemon.json にポリシーを書いて挙動を変えられます。
やりたいことによって、書くものが 2 通りあります。

容量の基準だけ決めたいなら、1 行で済みます。

{ "builder": { "gc": { "enabled": true, "defaultMaxUsedSpace": "10GB" } } }

厳密なディスククォータではなく、あくまで GC が走ったときの削除基準です(一時的に超えることはあります)。

「最終使用から 24 時間を過ぎたら削除する」のように経過時間でも決めたいなら、policyuntil フィルターを書きます。

{ "builder": { "gc": { "enabled": true, "policy": [ { "filter": ["until=24h"] } ] } } }

こちらは最小形です。
実際には容量の段も足すことになるので、使い分けと完全な例は後述の「結局どう設定するか」にまとめました。

ここに落ち着くまでに紆余曲折がありました。。。
はじめは公式ドキュメントの例を真似て、経過時間を keepDuration というキーで書いたのですが、これは daemon.json には存在しません。書いても黙って捨てられます。

調べていくと、GC の設定はドキュメントと実装が何箇所もずれていました。
この記事では、実際に効く設定と、そのずれを 5 つに分けてまとめます。

検証環境は次のとおりです。

  • Rancher Desktop 1.24.0(Docker Engine 29.5.3、docker ドライバー、BuildKit v0.30.0)
  • ホストは macOS

ソースの引用は、検証したバージョンに対応するタグ docker-v29.5.3 から取っています。
Docker Engine のリリースタグは docker-vX.Y.Z という形式で、vX.Y.Z とは別物です。

まず適用中のポリシーを見る

いきなり設定を書く前に、確認方法を押さえておきます。
docker buildx inspect で、実際に適用されているポリシーが見えます

docker buildx inspect
GC Policy rule#0:
 All:            false
 Filters:        type==source.local,type==exec.cachemount,type==source.git.checkout
 Keep Duration:  48h0m0s
 Max Used Space: 488.3MiB
GC Policy rule#1:
 All:            false
 Keep Duration:  1440h0m0s
 Max Used Space: 10GiB
GC Policy rule#2:
 All:            false
 Max Used Space: 10GiB
GC Policy rule#3:
 All:            true
 Max Used Space: 10GiB

書いた JSON がどう解釈されたかがそのまま出るので、これがいちばん確実な確認手段です。
keepDuration やフィルターの変換結果など、この記事で挙げるずれの多くはこの出力から見つけました。

なお daemon.json"10GB" と書いた結果が 10GiB と表示されているとおり、容量の値は 2 進で解釈されます。
10 進として扱われて 9.31GiB などに変換されることはありません。

キャッシュの使用量は docker buildx du で見ます。

docker buildx du
Shared:		507.6MB
Private:	419.5MB
Reclaimable:	927.1MB
Total:		927.1MB

この Shared と Private の区別が後で重要になります。

何も設定しなくても GC は動いている

前節の出力は、defaultMaxUsedSpace"10GB" だけを指定した状態のものです。
policy を書かなければ、既定の 4 段構成が使われます。

  1. 使用量が一時キャッシュ用の上限を超えているとき、48 時間より古い source.localexec.cachemountsource.git.checkout を削除する
  2. 使用量が上限を超えているとき、60 日(1440 時間)より古いキャッシュを削除する
  3. 使用量が上限を超えているとき、経過時間を問わず削除する
  4. それでも足りなければ、All: true として保護されている種別も含めて削除する

1 段目と 2 段目には「48 時間」「60 日」という、最終使用からの経過時間の条件があります。
そして全段に容量の条件が付いています
前節の出力で、Keep Duration を持つ 1 段目と 2 段目にも Max Used Space が並んでいるとおりです。

つまり 48 時間と 60 日は単純な有効期限ではありません。
容量の条件を満たしたときにはじめて、経過時間で絞り込んで削除されます。
ディスクに余裕があるうちは、60 日どころか何日経っても消えません。

実体は daemon/internal/builder-next/worker/gc.goDefaultGCPolicy です。
容量の既定値は、reservedSpacemaxUsedSpaceminFreeSpaceすべてゼロのときだけディスク容量から計算され、それぞれディスク全体の 10%、80%、20% になります。
実装にある 2GB という値は既定値ではなく、ディスク情報を読めなかったときのフォールバックです。

1 段目の Max Used Space488.3MiB と半端な値になっているのは、一時キャッシュ用の容量に 512MB(= 488.3MiB)の下限が設けられているためです。

なお公式ドキュメントには defaultKeepStorage の既定値が 20GB と書かれていますが、これは「Docker Desktop の場合」と明記された値です。
素の Docker Engine では上記のとおりディスクに対する割合で決まります。

放っておくとキャッシュが膨らんでいくのは、GC が動いていないからではありません。
経過時間の条件が 48 時間や 60 日と長く、容量の条件もディスクの割合なので、手元の感覚より上限が大きいからです。

設定ファイルの場所と反映のさせ方

GC ポリシーは、Docker デーモンの設定ファイル daemon.json に書きます。
場所は環境によって異なります。

  • Linux で dockerd を直接動かす場合/etc/docker/daemon.json
  • Linux の rootless モード~/.config/docker/daemon.jsonXDG_CONFIG_HOME が設定されていればそちらが優先)
  • Docker Desktop:Settings の Docker Engine タブに JSON エディタがある(実体は ~/.docker/daemon.json
  • Windows で dockerd を直接動かす場合%programdata%\docker\config\daemon.json

macOS や Windows では、dockerd 自体は Linux VM の中で動いています。
Docker Desktop なら GUI のエディタから編集するのが素直です。

今回使った Rancher Desktop にはその GUI がないため、VM 内の /etc/docker/daemon.json を直接編集しました。
VM には rdctl shell で入れます。

rdctl shell sudo vi /etc/docker/daemon.json

リロードでは反映されない

書いたら反映させます。
ここで、リロード(SIGHUP)では反映されない点に注意が必要です。

daemon.json の一部の設定は再起動なしに反映できるため、まずリロードを試しました。
dockerd に SIGHUP を送ると、読み込んでいる設定の全体がログにダンプされます。

# Rancher Desktop の場合
rdctl shell sudo sh -c 'kill -HUP $(pgrep -f "^/usr/bin/dockerd"); sleep 2; tail -3 /var/log/docker.log'

ログ上は成功しています。

level=info msg="Got signal to reload configuration" config-file=/etc/docker/daemon.json
level=info msg="Reloaded configuration" config="{... \"builder\":{\"GC\":{},\"Entitlements\":{}} ...}"

しかし設定ダンプは "builder":{"GC":{}} のままで、書いたポリシーが入っていません。
daemon/reload.go を見ると、リロード時に呼ばれるハンドラの一覧に builder 関連のものがありませんでした。
BuildKit の GC 設定はデーモンの起動時にしか読まれないということになります。

# Linux で dockerd を直接動かす場合
sudo systemctl restart docker
# Rancher Desktop の場合
rdctl shell sudo rc-service docker restart

Docker Desktop の場合は、Docker Engine タブの Apply & restart ボタンが再起動まで面倒を見てくれます。

GC は起動時に走る

削除がいつ起きるのかも確かめました。
GC はデーモンの起動時に走ります。

gc を無効にした状態で再起動を 3 回繰り返しても、キャッシュは 6 件 948.1MB のまま動きませんでした。
つまり再起動そのものがキャッシュを掃除するわけではありません。

一方、削除されるポリシーを入れて再起動すると、その場で減ります。
設定を変えたら再起動して docker buildx du を見る、という流れで確認できます。

BuildKit の実装(control/control.go)では、コントローラーの初期化時、つまりデーモンの起動時に 1 秒後の GC が予約されます。
ビルド(Solve)が終わったときにも同じように予約され、実行は 1 分に 1 回までスロットリングされます。
アイドルで待っていても減らないのはこのためです。

今回の検証では、起動時の GC だけでは削除されず、後続のビルドの後に追加で削除されたレコードもありました。
参照状態などによって、一度にすべてが削除対象になるとは限らないようです。

ずれ 1:keepDurationdaemon.json には存在しない

ここからが本題です。

最初に書いたのは、公式ドキュメントの例を真似た次のポリシーでした。

{
  "builder": {
    "gc": {
      "enabled": true,
      "policy": [
        { "keepDuration": "24h", "reservedSpace": "1GB" },
        { "reservedSpace": "10GB", "all": true }
      ]
    }
  }
}

これが効きません。
docker buildx inspect を見ると、Keep Duration の行がありません。

設定ダンプでも、読み込まれた内容から KeepDuration だけが落ちています。

"Policy": [
  { "Filter": [], "ReservedSpace": "1GB" },
  { "All": true, "Filter": [], "ReservedSpace": "10GB" }
]

理由は構造体を見ると分かります。

https://github.com/moby/moby/blob/docker-v29.5.3/daemon/config/builder.go

type BuilderGCRule struct {
	All           bool `json:",omitempty"`
	Filter        BuilderGCFilter
	ReservedSpace string `json:",omitempty"`
	MaxUsedSpace  string `json:",omitempty"`
	MinFreeSpace  string `json:",omitempty"`
}

KeepDuration というフィールドがありません。
Go の encoding/json は知らないキーを既定で無視するため、keepDuration は静かに捨てられます。

ポリシーを BuildKit に渡す getGCPolicy でも、渡しているのは 3 つの容量とフィルターだけです。

https://github.com/moby/moby/blob/docker-v29.5.3/daemon/internal/builder-next/controller.go

for i, p := range conf.GC.Policy {
	reservedSpace, maxUsedSpace, minFreeSpace, err := parseGCPolicy(p, "")
	...
	gcPolicy[i], err = toBuildkitPruneInfo(buildbackend.CachePruneOptions{
		All:           p.All,
		ReservedSpace: reservedSpace,
		MaxUsedSpace:  maxUsedSpace,
		MinFreeSpace:  minFreeSpace,
		Filters:       filters.Args(p.Filter),
	})

それでも公式ドキュメントの「Custom GC policies in the Docker daemon configuration file」には、keepDuration を使った daemon.json の例が載っています。

{ "reservedSpace": "50GB", "keepDuration": ["1440h"] },

この 1 行目は経過時間の条件として働かず、reservedSpace の部分だけが読まれます。

dockerd のリファレンスにある daemon.json の全体例にも keepDuration が載っており、こちらは "48h" と文字列、GC のページは ["1440h"] と配列です。
存在しないキーが、書き方まで揃わないまま両方のページに載っている状態です(2026 年 8 月時点)。

この乖離は 2023 年 11 月から issue として挙がっていました。

https://github.com/moby/moby/issues/46864

主題は「daemon.json の GC 設定は複雑なうえ、1 つのポリシーに複数のフィルターを書けない」という指摘で、BuildKit 側にある keepDuration 相当を書けないことも併せて挙げられています。
複数フィルターの件は後述の「ずれ 4」と同じ問題です。
2026 年 8 月現在も open のままです。

ずれ 2:経過時間は until フィルターで書く

では docker ドライバーで経過時間を指定できないのかというと、そうではありませんでした。
filteruntil を書きます。

{
  "builder": {
    "gc": {
      "enabled": true,
      "policy": [
        { "filter": ["until=24h"] }
      ]
    }
  }
}

これを入れて docker buildx inspect を見ると、Keep Duration に変換されています。

GC Policy rule#0:
 All:           false
 Filters:
 Keep Duration: 24h0m0.000000917s

フィルターは前節の getGCPolicy から toBuildkitPruneInfo に渡され、until はそこで経過時間として解釈されて、フィルターからは取り除かれます。
Filters が空になっているのはそのためです。

末尾に付く 0.000000917s は丸め誤差ではなく、until の値が絶対時刻を経由して期間に戻される際に、現在時刻を 2 回取得する間の実時間が上乗せされたものです。
実行するたびに端数が変わるのはこのためで、実用上は気にしなくて大丈夫です。

同じことは unused-for でも書けますが、実装では until が正式な名前で、unused-for は非推奨の別名です(daemon/internal/builder-next/builder.go)。
両方を同時に書くと、conflicting filters: "until" and "unused-for" というエラーで dockerd が起動しなくなります。
新しく設定するなら until を使ってください。

本当に効くのか確かめる

表示されるだけで実は効いていない、という可能性もあります。
経過時間と容量条件を変えながら、実際に削除されるか確かめました。

キャッシュは 6 件 948.1MB、うち Private が 629.3MB です。
docker buildx du --verbose で見ると、すべて最終使用が数分前でした。

ポリシー 結果
{"filter": ["unused-for=24h"], "maxUsedSpace": "100MB"} 削除なし(6 件 948.1MB のまま)
{"filter": ["unused-for=1s"], "maxUsedSpace": "100MB"} 6 件 948.1MB → 3 件 633.4MB
{"filter": ["unused-for=1s"]}(容量条件なし) 7 件 1.263GB → 3 件 633.4MB
{"filter": ["unused-for=1s"], "all": true} 3 件 633.4MB → 1 件 4.183MB

3 行目だけ開始時の件数が多いのは、2 行目で削除されたぶんを作り直してから試したためです。
また表は調査時のもので別名の unused-for を使っていますが、until に読み替えて問題ありません。

1 行目が要点です。
容量条件の判定対象になる Private が 629.3MB あり、上限の 100MB を大きく超えているのに、24 時間を指定すると削除されません。
数分前に使ったキャッシュが保護されているということで、経過時間の条件として機能しています。

3 行目のとおり、容量条件を書かなくても経過時間だけで削除されました。

4 行目の all については、既定の false だと一部の種別が保護されます。
unused-for=1s でも 3 件 633.4MB で止まったのは、mount / from exec ... という種別のレコードが残っていたからです。
all: true を足すとこれも消え、最後に 4.183MB のレコードが 1 件だけ残りました。
これは Shared: true、つまりイメージから参照されている層です(参照しているイメージを削除すると Sharedfalse に変わります)。

後述のとおり all: true は共有されているレコードも対象に含める実装なので、これが残る理由は共有そのものではないはずです。
かといって使用中だったわけでもなく、このレコードは Reclaimable: true でした(docker buildx duReclaimableInUse の否定です)。
デーモンのログに削除失敗のエラーも出ておらず、理由の特定までは至っていません。

ずれ 3:容量条件は Shared を除いた分に効く

容量の上限にも、ドキュメントに書かれていない挙動があります。

docker buildx du は使用量を Shared と Private に分けて出します。
Shared はイメージなどと共有している層、Private はビルドキャッシュが専有している分です。

意図的に内訳を作って試しました。

Shared:		507.6MB
Private:	419.5MB
Total:		927.1MB

この状態で上限を Total と Private の間の 600MB にしても、削除は起きませんでした。
300MB まで下げると動きました。

上限 Private との関係 結果
600MB 419.5MB < 600MB 削除されない
300MB 419.5MB > 300MB 7 件 927.1MB → 6 件 675.4MB

BuildKit の実装を見ると理由が分かります。
判定に使う totalSize を計算するとき、Shared なレコードを除外しています

https://github.com/moby/buildkit/blob/v0.30.0/cache/manager.go

totalSize := int64(0)
if opt.MaxUsedSpace != 0 || opt.ReservedSpace != 0 || opt.MinFreeSpace != 0 {
	du, err := cm.DiskUsage(ctx, client.DiskUsageInfo{})
	if err != nil {
		return err
	}
	for _, ui := range du {
		if ui.Shared {
			continue
		}
		totalSize += ui.Size
	}
}

つまり maxUsedSpace などの容量条件は、Shared を含めた合計ではなく専有分に対して評価されます。
容量条件がひとつも指定されていなければ、この計算自体が行われません。

冒頭の docker system df では Build Cache が SIZE 3.681GB、RECLAIMABLE 513.6MB で、今回の環境ではこの RECLAIMABLE が docker buildx du の Private と一致していました。
このとき上限を 1GB にしても、513.6MB は 1GB を下回るので何も起きません。

上限を決めるときは SIZE ではなく、docker buildx du の Private を見てください。

ずれ 4:filter は「1 要素 1 キー」で書く

filter は JSON の配列ですが、素直に要素を分けると起動しなくなることがあります。
書式ごとに試した結果です。

書き方 結果
["until=24h"] 起動する
["until=24h", "type=source.local"] 起動する
["type=source.local", "type=exec.cachemount"] 起動失敗(filters expect only one value
["until=24h,type=source.local"] 起動失敗

規則は「1 つの配列要素に 1 つのキー」です。

  • 同じキーで複数の値を指定するなら、カンマでつないで 1 要素にまとめる
  • 違うキーなら、要素を分ける
  • 違うキーをカンマでつないで 1 要素にすると起動しない

同じキーを並べるときは演算子を混ぜる

厄介なのはここからです。
同じキーをカンマで並べる場合、dockerd が起動しても GC の実行時に失敗することがあります。

["type=source.local,type=exec.cachemount"] と書いて inspect を見ると、こう表示されます。

Filters:       type==source.local,type=exec.cachemount

1 つ目だけ == に正規化され、2 つ目は = のままです。
この状態で GC が走ると、デーモンのログにエラーが出ます。

level=error msg="gc error: filters: parse error:
[type==source.local,type >|=|< exec.cachemount]: unsupported operator \"=\": invalid argument
failed to parse prune filters [type==source.local,type=exec.cachemount]"

パーサーは最初の条件にだけ = を補うので、書くときの演算子を混ぜる必要があります。
3 通り試した結果です。

書き方 inspect の表示 GC の実行時
すべて = type==source.local,type=exec.cachemount エラー(unsupported operator "="
1 つ目 =、2 つ目以降 == type==source.local,type==exec.cachemount 正常
すべて == type===source.local,type==exec.cachemount エラー(unsupported operator "==="

動くのは真ん中だけです。

"filter": ["type=source.local,type==exec.cachemount,type==source.git.checkout"]

見てのとおり実装の不整合に依存した書き方で、issue にもなっています。

https://github.com/moby/buildkit/issues/5581

2024 年 12 月に立てられた open の issue で、パーサーが最初の条件にだけ = を補っているのではないかと推測されています。

そして、この失敗は docker buildx inspect では検出できません。
inspect は変換結果を表示するだけで、GC 実行時のパースまでは通しません。
同じキーを複数書くなら、inspect に加えて、実際に GC を走らせたあとのデーモンのログまで確認してください。

ずれ 5:未知のキーは黙って無視される

keepDuration の件で分かるとおり、書いたキーが読まれているかは、書けたかどうかでは判断できません。
どこまで検出されるのか、ありえないキーを 2 か所に入れて試しました。

ポリシーの中に入れた場合は、何も起きません。

{ "keepDuration": "3h", "reservedSpace": "1GB", "totallyBogusKey": "xyz" }

dockerd は正常に起動し、ログに警告も出ませんでした。

一方、トップレベルに入れた場合は起動に失敗します。

{ "totallyBogusTopLevelKey": "xyz" }
unable to configure the Docker daemon with file /etc/docker/daemon.json:
the following directives don't match any configuration option: totallyBogusTopLevelKey

少なくとも builder.gc.policy の中では、未知のキーがエラーになりません。
キー名を間違えても起動できてしまう場所があるということです。

仕組みは daemon/config/config.go にあります。
未知のキーの検出は JSON を 1 段だけ平坦化してフラグ名と突き合わせる方式で、builder は平坦化の対象外かつ検証スキップの扱いです。
それより深い階層を検証する仕組みはなく、取り込みは素の json.Unmarshal なので、builder.gc.policy の中に何を書いても黙って無視されます。

キー名が新旧で揺れている領域なので、ここは効いてきます。
容量指定のキーは Docker Engine 28 以降で reservedSpacemaxUsedSpaceminFreeSpace が導入され、それ以前の keepStoragedefaultKeepStorage は旧来の名前になりました。

https://github.com/moby/moby/issues/48639

2024 年 10 月に立てられ、2025 年 2 月に close されました(マイルストーンは 28.0.0)。
BuildKit v0.17 の GC 設定に追随するためのものです。
公式ドキュメントの中で表記が揃っていないのはこの移行の名残で、GC のページdefaultKeepStoragedockerd のリファレンスの設定ファイル例は defaultReservedSpace になっています。

ただし旧来の名前が無視されるわけではありません。
defaultKeepStorage を書いたときは DefaultReservedSpace として読み込まれました。
エイリアスとして生きているキーと、そもそも存在しないキーが混在している、というのが実情です。

だからこそ docker buildx inspect で確かめる価値があります。

結局どう設定するか

用途に応じて 2 通りあります。

上限だけ決めたい場合

policy を書かず、容量のキーだけを指定します。

{
  "builder": {
    "gc": {
      "enabled": true,
      "defaultMaxUsedSpace": "10GB"
    }
  }
}

policy が空のままなので既定の 4 段構成が使われ、48 時間と 60 日の経過時間の条件も残ります。
これがいちばん簡単です。

注意点が 2 つあります。

ひとつは、これは厳密なディスククォータではないことです。
GC 実行時の削除基準なので、ビルド中などは一時的に超えます。
削除できないレコードがあれば、設定値まで下がりきらないこともあります。

もうひとつは、ディスク割合からの自動計算が 3 つの容量キーすべてゼロのときだけ行われる点です。
defaultMaxUsedSpace だけを書くと reservedSpaceminFreeSpace は 0 になり、既定の 10% と 20% は引き継がれません。
下限も設定したいなら、環境に合う値を明示します。

{
  "builder": {
    "gc": {
      "enabled": true,
      "defaultReservedSpace": "2GB",
      "defaultMaxUsedSpace": "10GB",
      "defaultMinFreeSpace": "5GB"
    }
  }
}

この 2GB は既定値ではなく、あくまで例です(前述のとおり、素の既定はディスクの 10% です)。

経過時間でも削除したい場合

until を使って policy を書きます。

{
  "builder": {
    "gc": {
      "enabled": true,
      "policy": [
        { "filter": ["until=24h"] },
        { "maxUsedSpace": "10GB" },
        { "maxUsedSpace": "10GB", "all": true }
      ]
    }
  }
}

1 段目は、最終使用から 24 時間を過ぎたキャッシュを削除します。
容量条件を書いていないので、ディスクに余裕があっても削除されます
2 段目は経過時間を問わない上限、3 段目はそれでも足りないときに対象を広げる段です。

all を安易に付けないのがポイントです。
all: true は「全ビルドキャッシュ」という意味ではなく、既定では保護されている internal や frontend のレコード、共有されているレコードまで対象に含めます。
1 段目に付けると、24 時間使わなかっただけでそこまで消えるので、再ビルドが重くなります。

policy を書くと既定の 4 段構成は置き換わります。
48 時間と 60 日の条件も、種別ごとの扱いも自分で書いた内容に差し替わるので、上限だけで足りるなら前者のほうが素直です。

どちらの場合も、書いたら再起動して docker buildx inspect で確認してください。

補足:docker-container ドライバーは別の設定になる

ここまでの daemon.json の設定が効くのは、docker ドライバーのビルダーだけです。
docker buildx create で作った docker-container ドライバーのビルダーは、コンテナの中で独立した BuildKit が動いており、キャッシュも別に持ちます。

手元のビルダーの種類は docker buildx ls の DRIVER 列で分かります。

NAME/NODE         DRIVER/ENDPOINT   STATUS    BUILDKIT   PLATFORMS
default*          docker                      v0.30.0    linux/amd64, linux/arm64

docker-container ドライバーの場合は buildkitd.toml で設定します。
そしてこちらには keepDuration がそのまま存在します

[worker.oci]
  gc = true
  reservedSpace = "10GB"

  [[worker.oci.gcpolicy]]
    keepDuration = "24h"
    reservedSpace = "1GB"

  [[worker.oci.gcpolicy]]
    all = true
    reservedSpace = "10GB"

公式ドキュメントにも「整数の秒数(例:172800)でも文字列(例:"48h")でもよい」と明記されています。
キー名も filter ではなく filters で、演算子は == です。

daemon.json の例に keepDuration が載ってしまっているのは、この BuildKit 側の設定と混ざったからではないかと想像しています。

おわりに

ビルドキャッシュの上限を宣言的に決めるだけなら defaultMaxUsedSpace を 1 行、経過時間でも削除したいなら policyuntil フィルターを書きます。
そこにたどり着くまでに引っかかった、ドキュメントと実装のずれをまとめます。

  • daemon.json のポリシーに keepDuration フィールドはなく、書いても未知のキーとして無視される
  • 経過時間を指定するなら filteruntil を使う。unused-for は非推奨の別名で、併記すると起動しない
  • 既定の 48 時間と 60 日は単純な有効期限ではなく、容量の条件と組み合わせて評価される
  • 容量条件は Shared を除いた専有分に対して評価される
  • filter は 1 つの配列要素に 1 つのキーしか書けない。同じキーを並べるときは 1 つ目を =、2 つ目以降を == にしないと GC の実行時に失敗する
  • builder.gc.policy の中はキー名を間違えても dockerd が起動してしまう
  • defaultMaxUsedSpace だけを書くと、reservedSpaceminFreeSpace は 0 になる

どれも「書けたのに動かない」という形で出るので原因が分かりにくいところでした。
docker buildx inspect で解釈結果を確かめる癖をつけておくと、この種の問題はかなり早く見つかります。

同じようにビルドキャッシュに悩んでいる方の参考になれば幸いです。

参考

この記事をシェアする

関連記事