APC 技術ブログ

株式会社エーピーコミュニケーションズの技術ブログです。

株式会社 エーピーコミュニケーションズの技術ブログです。

Artifact Registry で「コミットIDタグ」を無視して不要な Docker イメージを抽出・削除する

はじめに

こんにちは。クラウド事業部の野本です。

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 コマンドでなくても適用できます。

参考


  1. もし !A に相当する正規表現を自力で書けるのなら、 !~ に頼らなくても実現できます。