Claude CodeでECRのライフサイクルポリシーを書く — 消してはいけないタグを先に守る
ECRのライフサイクルポリシーは稼働中かどうかを見ません。残すタグを高い優先度で守る書き方と、プレビューの読み方、Claude Codeに任せる権限の線引きをまとめます。
ECR(Amazon Elastic Container Registry)のライフサイクルポリシーは、タグ・経過日数・本数の3つだけでイメージを選びます。ECSやKubernetesで動いているかどうかは、判断の材料に入りません。つまり「稼働中のイメージを除く」と書くことはできず、残したいものをタグで宣言し、高い優先度で守る書き方になります。
この記事では、守るルールを先頭に置いたポリシーの書き方、aws ecr start-lifecycle-policy-preview で削除対象を確かめる手順、稼働中のイメージと突き合わせる手順を順に扱います。Claude Codeには、案の作成とプレビューの実行だけを許可し、適用は人が押す構成にします。
ポリシーが見るもの、見ないもの
ポリシーのルールは、tagStatus・tagPatternList(またはtagPrefixList)・countType・countNumberなどの項目で構成されます。コンテナの実行状況や、タスク定義・Podからの参照を指定する項目はありません。
| 判断に使える | 判断に使えない |
|---|---|
タグの有無とパターン(tagStatus) | 判断に使えないECSのサービスが参照しているか |
作成からの日数(sinceImagePushed) | 判断に使えないKubernetesのPodが使っているか |
最後のpullからの日数(sinceImagePulled) | 判断に使えないデプロイの履歴やロールバック先 |
リポジトリ内の本数(imageCountMoreThan) | 判断に使えない本番の承認を通したかどうか |
評価の流れも先に押さえておきます。ECRは、すべてのルールを同時に評価し、そのあとルールの優先度に従って適用します。優先度の数字は小さいほど先に効きます。1枚のイメージを期限切れにできるルールは、ゼロか1つだけです。
ここから、守り方の核が出てきます。あるルールのタグ条件に合うイメージは、それより優先度の低いルールでは期限切れにされません。AWSの例でも、優先度1のprod*ルールが残したprod-2は、優先度2のbeta*ルールが対象にしても消えません。
消してはいけないタグを先頭で守るポリシー
稼働中のイメージを守る手段は、タグの規約です。本番に出すイメージには必ずprod-やv始まりのタグを付ける、というルールをCI側で決めておきます。タグを付ける側の設計は、ghcrに公開するワークフローのsha・semverタグの章と同じ考え方です。
そのうえで、次のポリシーを置きます。
{
"rules": [
{
"rulePriority": 1,
"description": "prod-* を守る",
"selection": {
"tagStatus": "tagged",
"tagPatternList": ["prod-*"],
"countType": "imageCountMoreThan",
"countNumber": 9999
},
"action": { "type": "expire" }
},
{
"rulePriority": 2,
"description": "v* を守る",
"selection": {
"tagStatus": "tagged",
"tagPatternList": ["v*"],
"countType": "imageCountMoreThan",
"countNumber": 9999
},
"action": { "type": "expire" }
},
{
"rulePriority": 3,
"description": "タグなしは7日で削除",
"selection": {
"tagStatus": "untagged",
"countType": "sinceImagePushed",
"countUnit": "days",
"countNumber": 7
},
"action": { "type": "expire" }
},
{
"rulePriority": 10,
"description": "残りは新しい30本だけ残す",
"selection": {
"tagStatus": "any",
"countType": "imageCountMoreThan",
"countNumber": 30
},
"action": { "type": "expire" }
}
]
}優先度1と2は「守る」ためのルールです。countNumberに十分大きい値を入れて、実際には何も期限切れにしないまま、該当イメージを後続のルールから隠します。この「大きな本数で守る」書き方はAWSの例に載っているものではなく、上の評価規則から組み立てた設計です。効いているかどうかは、必ず次のプレビューで確かめます。
ほかの3点にも理由があります。
tagStatusがanyのルールは、最後に評価されるよう最大の優先度番号にする。優先度10にしたのはこのためです- 複数のタグを1つの
tagPatternListに並べると、すべてに合うイメージだけが選ばれる(AND条件)。prod-*とv*を別ルールにしたのは、どちらか一方に合えば守りたいからです countNumberに0は指定できない。「全部消す」ルールを本数で書くことはできません
イメージに複数のタグが付いているときも、守りは働きます。pr-12とprod-3の両方が付いたイメージは、優先度1のルールに合うので、pr-*を消すルールを後ろに足しても対象から外れます。
Claude Codeに任せる範囲を権限で切る
ポリシーの下書きとプレビューまでをClaude Codeに任せ、適用と削除は人の確認に残します。.claude/settings.jsonに次の権限を置きます。
{
"permissions": {
"allow": [
"Bash(aws ecr describe-images *)",
"Bash(aws ecr start-lifecycle-policy-preview *)",
"Bash(aws ecr get-lifecycle-policy-preview *)",
"Bash(aws ecs describe-task-definition *)",
"Bash(kubectl get pods *)"
],
"ask": [
"Bash(aws ecr put-lifecycle-policy *)"
],
"deny": [
"Bash(aws ecr batch-delete-image *)",
"Bash(aws ecr delete-lifecycle-policy *)"
]
}
}ルールは、deny、ask、allowの順に評価され、最初に当たったものが結果を決めます。広いdenyの内側に狭いallowを置いて例外にすることはできません。aws ecr *を丸ごとdenyにして個別にallowを足す書き方は成立しないので、上のように個別のコマンドで書き分けます。
*はサブコマンドの後ろに置きます。Bash(aws ecr put-lifecycle-policy *)のように、何を許す・止めるかを決める語を*の前に書くのが決まりです。
もう1点、実運用で効くのがパイプです。|や&&で繋いだコマンドは、すべての部分が許可されていないとプロンプトが出ます。jqで整形するより、AWS CLIの--query(JMESPath)で絞るほうが、許可ルールを増やさずに済みます。
プレビューで削除対象を確かめる
ポリシーをpolicy.jsonに保存し、プレビューを開始します。
aws ecr start-lifecycle-policy-preview \
--repository-name app/api \
--lifecycle-policy-text file://policy.json--lifecycle-policy-textを省くと、リポジトリに適用済みのポリシーが評価されます。すでに動いているポリシーの影響を調べ直すときに使えます。ポリシー本文の長さは100〜30,720文字の範囲です。
開始直後のstatusはIN_PROGRESSです。取得側の状態にはIN_PROGRESS・COMPLETE・EXPIRED・FAILEDの4種類があるので、COMPLETEになるまで待ちます。
aws ecr get-lifecycle-policy-preview \
--repository-name app/api \
--query 'status'COMPLETEになったら、削除対象をタグ・ダイジェスト・push日時・適用されたルールの優先度で一覧にします。
aws ecr get-lifecycle-policy-preview \
--repository-name app/api \
--query 'previewResults[].{tags:imageTags,digest:imageDigest,pushed:imagePushedAt,rule:appliedRulePriority}' \
--output json見るべき点は3つです。
prod-*やv*のタグを持つイメージが1件も混ざっていないこと。混ざっていれば、守りのルールが想定どおりに効いていませんruleが10のイメージが、想定した「古い開発用イメージ」であることsummary.expiringImageTotalCountが、リポジトリの総数に対して極端に大きくないこと
稼働中のイメージと突き合わせる
タグ規約で守っていても、規約から外れたタグで本番に出ているイメージがあるかもしれません。プレビュー結果を、実際に動いているイメージの一覧と照合します。
ECSなら、タスク定義からイメージ参照を取り出します。
aws ecs describe-task-definition \
--task-definition api \
--query 'taskDefinition.containerDefinitions[].image' \
--output textKubernetesなら、全名前空間のPodが使うイメージをkubectlのjsonpathで取れます。
kubectl get pods --all-namespaces \
-o jsonpath="{.items[*].spec['initContainers', 'containers'][*].image}"出てきたイメージ名は、リポジトリ:タグの形か、リポジトリ@sha256:...のダイジェスト指定のどちらかです。プレビューのimageTagsとimageDigestの両方で照合します。タグだけで比べると、ダイジェスト指定のデプロイを見落とします。
Claude Codeには、この照合を頼めます。依頼の形は次のとおりです。
app/api のライフサイクルポリシーをプレビューして、previewResults の
imageTags と imageDigest が、ECS のタスク定義と kubectl の出力に
1件でも含まれていないか照合して。含まれていたら適用せず、
該当イメージとルールの優先度を一覧で報告して。照合で重なりが出たら、そのイメージのタグを守りのパターンに合わせて付け直すか、ポリシー側のパターンを足します。重なりが0件になったのを見てから、適用に進みます。
稼働中のものだけでなく、ロールバック先のタスク定義やDeploymentの直近リビジョンが参照するイメージも対象です。「今動いている」だけで判断すると、戻したときに取得できなくなります。ECSの直近リビジョンを何世代まで残すかは、チームの戻し方で決める項目です。
適用と、適用後に起きること
プレビューの結果に納得したら、put-lifecycle-policyで適用します。ここは権限をaskにしてあるので、人が承認します。
aws ecr put-lifecycle-policy \
--repository-name app/api \
--lifecycle-policy-text file://policy.json適用後、条件に合うイメージは24時間以内に期限切れになります。実行された操作はAWS CloudTrailにイベントとして記録されるので、あとから「何が消えたか」を追えます。
消すのではなく移したいときは、アクションをtransitionにしてアーカイブ用のストレージに送る書き方があります。アーカイブされたイメージをsinceImageTransitionedで期限切れにする場合、アーカイブに最低90日置く条件があります。本番のロールバック用イメージを消さずに安く持ちたいときの選択肢です。
つまずきやすい点
落とし穴
pull時刻で消すと、長く動いているイメージが対象になる
sinceImagePulledは、最後のpullからの日数で選びます。長期間再起動していないタスクやPodのイメージは、動いていてもpullされないので、古いpull時刻のまま残ります。稼働中の保護には使わず、prod-*の守りルールを前に置いてから使います。pull時刻は少なくとも24時間に1回の更新で、正確な最終pull時刻とずれることもあります。
- レプリケーションを使っているとき。ポリシーは作成したリージョンのリポジトリにしか効きません。レプリカ側にも別にポリシーを作ります
- マニフェストリスト(マルチアーキテクチャ)のイメージ。マニフェストリストが参照しているイメージは、リストが先に削除・アーカイブされないと期限切れになりません。参照されている側のタグが古くても、すぐには消えないことがあります
- ワイルドカードは1つの文字列に4個まで。
*a*b*c*d*eのようなパターンは無効です - プレビューの状態が
EXPIRED。時間が経ったプレビュー結果は使えなくなります。適用の直前にもう一度開始し直します
S3側の同じ作業は、S3ライフサイクルルールの記事で扱っています。AWS全体で未使用のものを洗い出す段階は、describe系だけ許可して削除はTerraformへ回す手順が前段になります。ECSタスク側の見直しはFargateタスク定義の記事にあります。
まとめ
ECRのライフサイクルポリシーは、稼働状況を見ないので、残したいものはタグ規約と優先度で守ります。優先度の高いルールに合うイメージは、低いルールでは消えません。この性質を使い、prod-*やv*を先頭の守りルールに置いてから、タグなしと古い開発用イメージを消すルールを足します。
Claude Codeには、案の作成、プレビューの実行、稼働中イメージとの照合までを任せられます。適用をask、削除系をdenyにし、さらにIAMで削除権限を持たせない構成にすれば、プレビューで確かめる前に消える事故は起こしにくくなります。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- code.claude.com/docs/en/permissions
- docs.aws.amazon.com/AmazonECR/latest/userguide/LifecyclePolicies.html
- docs.aws.amazon.com/AmazonECR/latest/userguide/lifecycle_policy_parameters.html
- docs.aws.amazon.com/AmazonECR/latest/userguide/lifecycle_policy_examples.html
- docs.aws.amazon.com/AmazonECR/latest/userguide/lpp_creation.html
- docs.aws.amazon.com/cli/latest/reference/ecr/get-lifecycle-policy-preview.html