ECR: Repository Settings and Lifecycle Policy

Repository

SettingLab / prod
Scan on pushtrue (basic). Registry-level enhanced scanning (Inspector) is the current default to configure at the registry, not only per repo.
Resource tagsEnvironment=Prod, ManagedBy=Manual
Tag mutabilityImmutable for prod (retag fails with ImageTagAlreadyExistsException). Mutable only if you need overwrite (e.g. pull-through cache). Mutability is repo-wide.
aws ecr create-repository \
  --repository-name myapp \
  --image-scanning-configuration scanOnPush=true \
  --image-tag-mutability IMMUTABLE \
  --tags Key=Environment,Value=Prod Key=ManagedBy,Value=Manual

Lifecycle policy

Rules run by rulePriority (lower first). expire deletes the image, not a single tag.

Do not put test-, dev-, and feature- in one tagPrefixList. ECR selects images that match all prefixes on that rule. Same for prod- and v. One prefix per tagged rule.

PriorityMatchAction
1UntaggedExpire after 7 days
2Tags test-*Expire after 14 days
3Tags dev-*Expire after 14 days
4Tags feature-*Expire after 14 days
5Tags prod-*Keep 20 newest (imageCountMoreThan)
6Tags v* (prefix v)Keep 20 newest

Prefix v matches v1.2.3 and also vendor-foo. Use v only if that is acceptable, or switch to tagPatternList (e.g. v[0-9]*) if the registry supports it for your rule.

{
  "rules": [
    {
      "rulePriority": 1,
      "description": "Untagged images (dangling layers) after 7 days",
      "selection": {
        "tagStatus": "untagged",
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 7
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 2,
      "description": "test- tags after 14 days",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["test-"],
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 14
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 3,
      "description": "dev- tags after 14 days",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["dev-"],
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 14
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 4,
      "description": "feature- tags after 14 days",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["feature-"],
        "countType": "sinceImagePushed",
        "countUnit": "days",
        "countNumber": 14
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 5,
      "description": "Keep 20 newest prod- tags",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["prod-"],
        "countType": "imageCountMoreThan",
        "countNumber": 20
      },
      "action": { "type": "expire" }
    },
    {
      "rulePriority": 6,
      "description": "Keep 20 newest tags with prefix v",
      "selection": {
        "tagStatus": "tagged",
        "tagPrefixList": ["v"],
        "countType": "imageCountMoreThan",
        "countNumber": 20
      },
      "action": { "type": "expire" }
    }
  ]
}
aws ecr put-lifecycle-policy \
  --repository-name myapp \
  --lifecycle-policy-text file://ecr-lifecycle.json

References