はじめに
こんにちは。クラウド事業部の野本です。
Google Cloud の Artifact Registry へ、CIでビルドした Docker イメージをガンガン保存していたら、不要なバージョンが大量に溜まってしまいました。
不要なバージョンにタグが付いていなければ簡単に抽出・削除できるのですが、CIの際に機械的に付与しているタグが残っていて、「タグの付いていないイメージ」といった単純な判別ができない状態でした。
頑張って対応を考えた結果、 gcloud コマンドで「特定のパターンのタグは無視したうえでタグの付いていないイメージを抽出する」方法が分かったため、その方法と考え方を記載します。
したいこと
Artifact Registry に、以下のように様々なタグが付いた Docker イメージが保存されています。このうち不要になったイメージを抽出したい(そして削除したい)です。
DIGEST CREATE_TIME TAGS sha256:07d7... 2026-01-03T08:06:47 49c0ce6... ← 不要 sha256:152b... 2026-01-06T20:23:39 v1.2.1 sha256:357b... 2026-01-10T10:01:31 96eaf53... ← 不要 sha256:3779... 2026-01-13T21:30:01 af2e201... ← 不要 sha256:40e7... 2026-01-18T13:16:55 ← 不要 sha256:54b6... 2026-01-15T03:21:26 ebae477...,feature... sha256:5e68... 2026-01-22T02:42:48 998b9a0...,fix-abs... sha256:79d7... 2026-01-17T13:33:11 e2ee02f... ← 不要 sha256:86e0... 2026-01-21T01:48:48 751758e...,v1.2.4 sha256:8714... 2026-01-08T08:16:20 4f702f3...,test_in... ...
通常、 pull して利用するイメージには latest など分かりやすいタグを付けるため、新しいバージョンをビルドすることでタグが外れた古いイメージは不要となり削除できます。タグの無いイメージを削除する方法を調べると、クリーンアップポリシーや gcloud コマンドなど出てきます。特にコマンドの場合は --filter='-tags:*' のように条件を指定することで抽出できます。
しかし今回は 49c0ce6... のようなタグが大量に残っています。ビルドの際に分かりやすい名前のタグに加えて、運用上の都合で Git のコミットID もタグとして付与したら、いつの間にかこんなことになっていました。新しいバージョンには異なるタグが付くため、古いイメージから外れることがありません。このタグはイメージを残すかどうかに関与しないので、うまく無視して判定する必要があります。
(こんなややこしいことを考えるより、リリースタグの無い古いイメージを全て消す運用にしたほうが楽ですが)
解決策
以下のコマンドは、 ${IMAGE_PATH} に保存されたイメージのうち、タグが付いていないかコミットID形式(十六進40桁)のみ付いているものを列挙・削除します。(安全のため作成から90日超が経過したものに絞っています)
【注意】 delete 操作があるため、まずは list だけ実行して正しく抽出できているか確認するのがおすすめです。
# IMAGE_PATH='<location>-docker.pkg.dev/<project>/<repository>/<image>' gcloud artifacts docker images list "${IMAGE_PATH}" \ --include-tags \ --filter='-tags !~ ^[0-9a-f]{40}$ AND createTime < -P90D' \ --format='get(version)' \ > images-to-delete.txt cat images-to-delete.txt \ | while read digest; do gcloud artifacts docker images delete "${IMAGE_PATH}@${digest}" \ --async --delete-tags --quiet done
ポイントは、コミットIDのタグにマッチする正規表現 ^[0-9a-f]{40}$ の使用時に、演算子の否定 !~ と条件式の否定 - を使っていることです。
仕組み
抽出条件の整理
「特定のパターンのタグは無視したうえでタグの付いていないイメージ」という抽出条件だとややこしいので、もっと簡潔にできないか整理してみます。
個々のタグは、パターンを指定すれば「マッチするもの」と「マッチしないもの」の2通りに分類できます。コミットIDかどうか判別するのは、タグの名前が十六進40桁かどうかを正規表現 ^[0-9a-f]{40}$ で判定すればいいでしょう。
一方で Docker イメージには複数のタグを付けられるため、コミットIDのタグと他のタグが両方付いていることもあれば、タグが全く無いこともあります。イメージがどう分類されるのか、以下のベン図で確認します。

- 図右 Images で、
Aはパターン A にマッチするタグを持つイメージを、
!Aはパターン A にマッチしないタグを持つイメージを表しています - それらの共通部分は、両方のタグを持つイメージです
- 両方の円の外側は、タグを持たないイメージです
今回削除対象として抽出したいイメージは、コミットID(パターン A)のタグを無視したときにタグの付いていないイメージでした。これは A を無視すればいいので !A の外側 ということになります。言葉で言い直すと 「パターン A 以外のタグが付いていないイメージ」 です。
条件式の構築
整理した抽出条件を gcloud コマンドの --filter オプションで指定する方法を考えます。正規表現のマッチを使う記法は key ~ regex です。
否定を作る方法は、頭に - (または NOT)を付ける方法と、演算子を !~ に置き換える方法があります。これらは異なる動作をするので、ベン図で確認してみます。赤い網掛け領域は条件式によって抽出されるイメージです。

- 左上の
tags ~ Aは正規表現で普通にマッチさせた場合です。
中央の共通部分も抽出されているため、これでは消さないタグを持つイメージまで削除してしまいます。 - 右上の
- tags ~ Aは条件全体を否定したものです。
選ぶ領域が完全に反転しています。 - 左下の
tags !~ Aは正規表現を否定したものです。
これは-と異なり、選ぶグループが反転して!Aへ移っています。1 - そのため否定を組み合わせた右下の
- tags !~ Aは、消さないタグを持つイメージを適切に回避できています。
適用例
冒頭のイメージ一覧に対してこの方法で抽出すると、以下のようにタグが付いていないかコミットID形式のみ付いているものだけ抽出されます。
gcloud artifacts docker images list "${IMAGE_PATH}" \ --include-tags \ --filter='-tags !~ ^[0-9a-f]{40}$' \ --format='table(version.trailoff(14), createTime, tags.*trailoff(10).list())' \ --limit=5
DIGEST CREATE_TIME TAGS sha256:07d7... 2026-01-03T08:06:47 49c0ce6... sha256:357b... 2026-01-10T10:01:31 96eaf53... sha256:3779... 2026-01-13T21:30:01 af2e201... sha256:40e7... 2026-01-18T13:16:55 sha256:79d7... 2026-01-17T13:33:11 e2ee02f...
まとめ
Artifact Registry に保存された Docker イメージを抽出する際に、「指定したパターン以外のタグが付いていない」というややこしい条件を gcloud コマンドで実現する方法を紹介しました。イメージに複数のタグを付けられる関係で正規表現の否定 !~ と条件式の否定 - が異なる結果となることを利用しました。
この方法は条件式の考え方がメインのため、同じようなフィルタ条件を作れる場面であれば、 Docker イメージ以外のリソースに対してでも、さらには gcloud コマンドでなくても適用できます。
参考
- Google Cloud SDK リファレンス
-
もし
!Aに相当する正規表現を自力で書けるのなら、!~に頼らなくても実現できます。↩