Gallery image version past its end-of-life date, still replicated
A Compute Gallery image version whose own declared end-of-life date has passed while copies are still stored in every target region, which Azure does not clean up.
Why it happens
A Compute Gallery image version is billed as a snapshot for every replica in every region it is replicated to, and that continues for as long as the version exists. Azure documents an end-of-life date for exactly this situation and then does nothing with it: end-of-life dates are informational, and virtual machines can still be created from a version past that date, so the platform will never remove one. This rule therefore reports only versions whose OWNER already declared them finished with and which are still being stored, which is a fact from the record rather than a judgement about staleness. It deliberately does not claim the version is unused, because nothing in Azure says when an image version last created a machine: there is no usage field on the resource and no metric for the type at all. It also claims no saving. Billing is based on the occupied size of the image rather than the declared one, and the occupied size is not published anywhere in the resource record, so the only figure available is a ceiling, and a ceiling presented as a saving would overstate what deleting the version returns. The replica count is the sum across target regions, using each region own count where it sets one and the common count where it does not, because quoting the common count alone understates a widely replicated image by the number of regions. Deleting an image version is permanent unless the preview soft-delete feature has been registered in advance, so this reports and a person decides. Marking a version as excluded from latest does not stop any of this: it changes only which version the latest alias resolves to, and every replica carries on being stored.
Paste this into Resource Graph Explorer in the Azure portal.
resources
| where type =~ 'microsoft.compute/galleries/images/versions'
// Scope below matches CloudFinOpsPro policy DEFAULTS: exclusions.protectTag, environment.tagKeys,
// environment.allowedTagValues, and allowUntagged false so an untagged resource counts as production.
// If your policy.yaml differs, edit the three lines below or this will disagree with your own agent.
| where tolower(tostring(coalesce(tags['cfop:protect'], ''))) !in ('true', 'yes', '1')
| extend cfopEnv = tolower(trim(' ', tostring(coalesce(tags['Environment'], tags['environment'], tags['env'], ''))))
| where cfopEnv in ('dev', 'development', 'test', 'qa', 'sandbox', 'nonprod', 'non-prod', 'staging')
| extend eol = todatetime(properties.publishingProfile.endOfLifeDate)
| where isnotnull(eol) and eol < now()
| extend regions = properties.publishingProfile.targetRegions
| project version = name, resourceGroup,
endOfLife = eol,
regionCount = array_length(regions),
commonReplicaCount = toint(properties.publishingProfile.replicaCount)
| order by endOfLife ascCopies held is the sum across target regions, using each region's own regionalReplicaCount where it sets one and commonReplicaCount where it does not, so a ten-region image at three each holds thirty. Azure never deletes past the end-of-life date; it is informational only.
Applies to microsoft.compute/galleries/images/versionsWhat the agent does about it
A Compute Gallery image version whose own declared end-of-life date has passed while copies are still stored in every target region, which Azure does not clean up.
It will not fix this one
It reports this and takes no action. A check only earns the right to change something once the action behind it can re-read live state before it fires and knows the way back.
Related checks
Idle virtual machine
A running VM whose CPU and network have been near zero for the whole window.
Oversized virtual machine
A running VM whose sustained peak fits comfortably inside the next size down.
Unattached managed disk
A managed disk attached to nothing, billing its full provisioned size every month.