Delete container images in Azure Container Registry

To maintain the size of your Azure container registry, you should periodically delete stale image data. While some container images deployed into production may require longer-term storage, others can typically be deleted more quickly. For example, in an automated build and test scenario, your registry can quickly fill with images that might never be deployed, and can be purged shortly after completing the build and test pass.

Because you can delete image data in several different ways, it's important to understand how each delete operation affects storage usage. This article first introduces the components of a Docker registry and container images, then covers several methods for deleting image data. Sample scripts are provided to help automate delete operations.

Registry

A container registry is a service that stores and distributes container images. Docker Hub is a public Docker container registry, while Azure Container Registry provides private Docker container registries in Azure.

Repository

Container registries manage repositories, collections of container images with the same name, but different tags. For example, the following three images are in the "acr-helloworld" repository:

acr-helloworld:latest
acr-helloworld:v1
acr-helloworld:v2

Repository names can also include namespaces. Namespaces allow you to group images using forward slash-delimited repository names, for example:

marketing/campaign10-18/web:v2
marketing/campaign10-18/api:v3
marketing/campaign10-18/email-sender:v2
product-returns/web-submission:20180604
product-returns/legacy-integrator:20180715

Components of an image

A container image within a registry is associated with one or more tags, has one or more layers, and is identified by a manifest. Understanding how these components relate to each other can help you determine the best method for freeing space in your registry.

Tag

An image's tag specifies its version. A single image within a repository can be assigned one or many tags, and may also be "untagged." That is, you can delete all tags from an image, while the image's data (its layers) remain in the registry.

The repository (or repository and namespace) plus a tag defines an image's name. You can push and pull an image by specifying its name in the push or pull operation.

In a private registry like Azure Container Registry, the image name also includes the fully qualified name of the registry host. The registry host for images in ACR is in the format acrname.azurecr.io (all lowercase). For example, the full name of the first image in the "marketing" namespace in the previous section would be:

myregistry.azurecr.io/marketing/campaign10-18/web:v2

For a discussion on image tagging best practices, see the Docker Tagging: Best practices for tagging and versioning docker images blog post on MSDN.

Layer

Images are made up of one or more layers, each corresponding to a line in the Dockerfile that defines the image. Images in a registry share common layers, increasing storage efficiency. For example, several images in different repositories might share the same Alpine Linux base layer, but only one copy of that layer is stored in the registry.

Layer sharing also optimizes layer distribution to nodes with multiple images sharing common layers. For example, if an image already on a node includes the Alpine Linux layer as its base, the subsequent pull of a different image referencing the same layer doesn't transfer the layer to the node. Instead, it references the layer already existing on the node.

Manifest

Each container image pushed to a container registry is associated with a manifest. The manifest, generated by the registry when the image is pushed, uniquely identifies the image and specifies its layers. You can list the manifests for a repository with the Azure CLI command az acr repository show-manifests:

az acr repository show-manifests --name <acrName> --repository <repositoryName>

For example, listing the manifest digests for the "acr-helloworld" repository:

$ az acr repository show-manifests --name myregistry --repository acr-helloworld
[
  {
    "digest": "sha256:0a2e01852872580b2c2fea9380ff8d7b637d3928783c55beb3f21a6e58d5d108",
    "tags": [
      "latest",
      "v3"
    ],
    "timestamp": "2018-07-12T15:52:00.2075864Z"
  },
  {
    "digest": "sha256:3168a21b98836dda7eb7a846b3d735286e09a32b0aa2401773da518e7eba3b57",
    "tags": [
      "v2"
    ],
    "timestamp": "2018-07-12T15:50:53.5372468Z"
  },
  {
    "digest": "sha256:7ca0e0ae50c95155dbb0e380f37d7471e98d2232ed9e31eece9f9fb9078f2728",
    "tags": [
      "v1"
    ],
    "timestamp": "2018-07-11T21:38:35.9170967Z"
  }
]

The manifest discussed here is different from the image manifest you can view in the Azure portal or with docker manifest inspect. In the following section, "manifest digest" refers to the digest generated by the push operation, not the config.digest in the image manifest. You can pull and delete images by manifest digest, not config.digest. The following image illustrates the two types of digests.

Manifest digest and config.digest in the Azure portal

Manifest digest

Manifests are identified by a unique SHA-256 hash, or manifest digest. Each image--whether tagged or not--is identified by its digest. The digest value is unique even if the image's layer data is identical to that of another image. This mechanism is what allows you to repeatedly push identically tagged images to a registry. For example, you can repeatedly push myimage:latest to your registry without error because each image is identified by its unique digest.

You can pull an image from a registry by specifying its digest in the pull operation. Some systems may be configured to pull by digest because it guarantees the image version being pulled, even if an identically tagged image is subsequently pushed to the registry.

For example, pulling an image from the "acr-helloworld" repository by manifest digest:

$ docker pull myregistry.azurecr.io/acr-helloworld@sha256:0a2e01852872580b2c2fea9380ff8d7b637d3928783c55beb3f21a6e58d5d108

Important

If you repeatedly push modified images with identical tags, you might create orphaned images--images that are untagged, but still consume space in your registry. Untagged images are not shown in the Azure CLI or in the Azure portal when you list or view images by tag. However, their layers still exist and consume space in your registry. The Delete untagged images section of this article discusses freeing space used by untagged images.

Delete image data

You can delete image data from your container registry in several ways:

  • Delete a repository: Deletes all images and all unique layers within the repository.
  • Delete by tag: Deletes an image, the tag, all unique layers referenced by the image, and all other tags associated with the image.
  • Delete by manifest digest: Deletes an image, all unique layers referenced by the image, and all tags associated with the image.

Delete repository

Deleting a repository deletes all of the images in the repository, including all tags, unique layers, and manifests. When you delete a repository, you recover the storage space used by the images that were in that repository.

The following Azure CLI command deletes the "acr-helloworld" repository and all tags and manifests within the repository. If layers referenced by the deleted manifests are not referenced by any other images in the registry, their layer data is also deleted.

 az acr repository delete --name myregistry --repository acr-helloworld

Delete by tag

You can delete individual images from a repository by specifying the repository name and tag in the delete operation. When you delete by tag, you recover the storage space used by any unique layers in the image (layers not shared by any other images in the registry).

To delete by tag, use az acr repository delete and specify the image name in the --image parameter. All layers unique to the image, and any other tags associated with the image are deleted.

For example, deleting the "acr-helloworld:latest" image from registry "myregistry":

$ az acr repository delete --name myregistry --image acr-helloworld:latest
This operation will delete the manifest 'sha256:0a2e01852872580b2c2fea9380ff8d7b637d3928783c55beb3f21a6e58d5d108' and all the following images: 'acr-helloworld:latest', 'acr-helloworld:v3'.
Are you sure you want to continue? (y/n): y

Tip

Deleting by tag shouldn't be confused with deleting a tag (untagging). You can delete a tag with the Azure CLI command az acr repository untag. No space is freed when you untag an image because its manifest and layer data remain in the registry. Only the tag reference itself is deleted.

Delete by manifest digest

A manifest digest can be associated with one, none, or multiple tags. When you delete by digest, all tags referenced by the manifest are deleted, as is layer data for any layers unique to the image. Shared layer data is not deleted.

To delete by digest, first list the manifest digests for the repository containing the images you wish to delete. For example:

$ az acr repository show-manifests --name myregistry --repository acr-helloworld
[
  {
    "digest": "sha256:0a2e01852872580b2c2fea9380ff8d7b637d3928783c55beb3f21a6e58d5d108",
    "tags": [
      "latest",
      "v3"
    ],
    "timestamp": "2018-07-12T15:52:00.2075864Z"
  },
  {
    "digest": "sha256:3168a21b98836dda7eb7a846b3d735286e09a32b0aa2401773da518e7eba3b57",
    "tags": [
      "v2"
    ],
    "timestamp": "2018-07-12T15:50:53.5372468Z"
  }
]

Next, specify the digest you wish to delete in the az acr repository delete command. The format of the command is:

az acr repository delete --name <acrName> --image <repositoryName>@<digest>

For example, to delete the last manifest listed in the preceding output (with the tag "v2"):

$ az acr repository delete --name myregistry --image acr-helloworld@sha256:3168a21b98836dda7eb7a846b3d735286e09a32b0aa2401773da518e7eba3b57
This operation will delete the manifest 'sha256:3168a21b98836dda7eb7a846b3d735286e09a32b0aa2401773da518e7eba3b57' and all the following images: 'acr-helloworld:v2'.
Are you sure you want to continue? (y/n): y

The acr-helloworld:v2 image is deleted from the registry, as is any layer data unique to that image. If a manifest is associated with multiple tags, all associated tags are also deleted.

List digests by timestamp

To maintain the size of a repository or registry, you might need to periodically delete manifest digests older than a certain date.

The following Azure CLI command lists all manifest digest in a repository older than a specified timestamp, in ascending order. Replace <acrName> and <repositoryName> with values appropriate for your environment. The timestamp could be a full date-time expression or a date, as in this example.

az acr repository show-manifests --name <acrName> --repository <repositoryName> \
--orderby time_asc -o tsv --query "[?timestamp < '2019-04-05'].[digest, timestamp]"

Delete digests by timestamp

After identifying stale manifest digests, you can run the following Bash script to delete manifest digests older than a specified timestamp. It requires the Azure CLI and xargs. By default, the script performs no deletion. Change the ENABLE_DELETE value to true to enable image deletion.

Warning

Use the following sample script with caution--deleted image data is UNRECOVERABLE. If you have systems that pull images by manifest digest (as opposed to image name), you should not run these scripts. Deleting the manifest digests will prevent those systems from pulling the images from your registry. Instead of pulling by manifest, consider adopting a unique tagging scheme, a recommended best practice.

#!/bin/bash

# WARNING! This script deletes data!
# Run only if you do not have systems
# that pull images via manifest digest.

# Change to 'true' to enable image delete
ENABLE_DELETE=false

# Modify for your environment
# TIMESTAMP can be a date-time string such as 2019-03-15T17:55:00.
REGISTRY=myregistry
REPOSITORY=myrepository
TIMESTAMP=2019-04-05  

# Delete all images older than specified timestamp.

if [ "$ENABLE_DELETE" = true ]
then
    az acr repository show-manifests --name $REGISTRY --repository $REPOSITORY \
    --orderby time_asc --query "[?timestamp < '$TIMESTAMP'].digest" -o tsv \
    | xargs -I% az acr repository delete --name $REGISTRY --image $REPOSITORY@% --yes
else
    echo "No data deleted."
    echo "Set ENABLE_DELETE=true to enable deletion of these images in $REPOSITORY:"
    az acr repository show-manifests --name $REGISTRY --repository $REPOSITORY \
   --orderby time_asc --query "[?timestamp < '$TIMESTAMP'].[digest, timestamp]" -o tsv
fi

Delete untagged images

As mentioned in the Manifest digest section, pushing a modified image using an existing tag untags the previously pushed image, resulting in an orphaned (or "dangling") image. The previously pushed image's manifest--and its layer data--remains in the registry. Consider the following sequence of events:

  1. Push image acr-helloworld with tag latest: docker push myregistry.azurecr.io/acr-helloworld:latest

  2. Check manifests for repository acr-helloworld:

    $ az acr repository show-manifests --name myregistry --repository acr-helloworld
    [
      {
        "digest": "sha256:d2bdc0c22d78cde155f53b4092111d7e13fe28ebf87a945f94b19c248000ceec",
        "tags": [
          "latest"
        ],
        "timestamp": "2018-07-11T21:32:21.1400513Z"
      }
    ]
    
  3. Modify acr-helloworld Dockerfile

  4. Push image acr-helloworld with tag latest: docker push myregistry.azurecr.io/acr-helloworld:latest

  5. Check manifests for repository acr-helloworld:

    $ az acr repository show-manifests --name myregistry --repository acr-helloworld
    [
      {
        "digest": "sha256:7ca0e0ae50c95155dbb0e380f37d7471e98d2232ed9e31eece9f9fb9078f2728",
        "tags": [
          "latest"
        ],
        "timestamp": "2018-07-11T21:38:35.9170967Z"
      },
      {
        "digest": "sha256:d2bdc0c22d78cde155f53b4092111d7e13fe28ebf87a945f94b19c248000ceec",
        "tags": [],
        "timestamp": "2018-07-11T21:32:21.1400513Z"
      }
    ]
    

As you can see in the output of the last step in the sequence, there is now an orphaned manifest whose "tags" property is an empty list. This manifest still exists within the registry, along with any unique layer data that it references. To delete such orphaned images and their layer data, you must delete by manifest digest.

List untagged images

You can list all untagged images in your repository using the following Azure CLI command. Replace <acrName> and <repositoryName> with values appropriate for your environment.

az acr repository show-manifests --name <acrName> --repository <repositoryName> --query "[?tags[0]==null].digest"

Delete all untagged images

Warning

Use the following sample scripts with caution--deleted image data is UNRECOVERABLE. If you have systems that pull images by manifest digest (as opposed to image name), you should not run these scripts. Deleting untagged images will prevent those systems from pulling the images from your registry. Instead of pulling by manifest, consider adopting a unique tagging scheme, a recommended best practice.

Azure CLI in Bash

The following Bash script deletes all untagged images from a repository. It requires the Azure CLI and xargs. By default, the script performs no deletion. Change the ENABLE_DELETE value to true to enable image deletion.

#!/bin/bash

# WARNING! This script deletes data!
# Run only if you do not have systems
# that pull images via manifest digest.

# Change to 'true' to enable image delete
ENABLE_DELETE=false

# Modify for your environment
REGISTRY=myregistry
REPOSITORY=myrepository

# Delete all untagged (orphaned) images
if [ "$ENABLE_DELETE" = true ]
then
    az acr repository show-manifests --name $REGISTRY --repository $REPOSITORY  --query "[?tags[0]==null].digest" -o tsv \
    | xargs -I% az acr repository delete --name $REGISTRY --image $REPOSITORY@% --yes
else
    echo "No data deleted. Set ENABLE_DELETE=true to enable image deletion."
fi

Azure CLI in PowerShell

The following PowerShell script deletes all untagged images from a repository. It requires PowerShell and the Azure CLI. By default, the script performs no deletion. Change the $enableDelete value to $TRUE to enable image deletion.

# WARNING! This script deletes data!
# Run only if you do not have systems
# that pull images via manifest digest.

# Change to '$TRUE' to enable image delete
$enableDelete = $FALSE

# Modify for your environment
$registry = "myregistry"
$repository = "myrepository"

if ($enableDelete) {
    az acr repository show-manifests --name $registry --repository $repository --query "[?tags[0]==null].digest" -o tsv `
    | %{ az acr repository delete --name $registry --image $repository@$_ --yes }
} else {
    Write-Host "No data deleted. Set `$enableDelete = `$TRUE to enable image deletion."
}

Next steps

For more information about image storage in Azure Container Registry see Container image storage in Azure Container Registry.