The Azure Container Registry is based on the open-source Docker Registry 2.0 and is used to store container images and related artifacts. In addition to Docker-compatible container images, Azure Container Registry also supports Helm charts and Open Container Initiative (OCI) image formats. The Azure Container Registry can be used for different scenarios. For example, developers can use a container registry in their CI/CD pipelines to push container images. It’s also possible to configure ACR Tasks, which is a suite of features within Azure Container Registry. ACR Tasks provides container image building in the cloud and can automate OS and framework patching. You can use ACR Tasks to automate image builds when your team commit code to a Git repository, or to automatically rebuild application images when their base images are updated. For example, with base image update triggers, you can automate your OS and application framework patching workflow.
Table of Contents
Important Features Of The Azure Container Registry
Azure Container Registry serves as a catalog for your container images. There are serveral ways to authenticate with an Azure Container Registry. For example, users can authenticate to a registry directly via individual login, but applications and services authenticate by using a service principal. If you want an Azure service to access your Azure Container Registry, there is a good chance that this service can use a managed identity for authentication. In that case you don’t have to manage the credentials yourself, which is even better. For all these scenarios, you can use role-based access control (RBAC) to control what the user, application or service is allowed to access.
You can limit access to the registry even further by assigning virtual network private IP addresses to the registry endpoints and using Azure Private Link. This way, all network traffic between clients on the virtual network and the registry’s private endpoints traverses the virtual network and a private link on the Microsoft backbone network. There is no exposure to the public internet.
The Azure Container Registry also offers Geo-replication. By using Geo-replication, you can host your container images in multiple regions around the world. The replication happens automatically for you while you manage it as a single registry. A geo-replicated registry improves performance and reliability of regional deployments with network-close registry access. It provides a highly available registry that is resilient to regional outages. It also reduces data transfer costs because image layers are pulled from a local, replicated registry in the same or nearby region as your container host.
Which Service Tier Should You Choose
There are three service tiers available to choose from: basic, standard and premium. The basic service tier is a cost-optimized entry point for developers to get started. It has the same programmatic capabilities as the standard and premium service tiers. The standard service tier should be used for most production scenarios. It offers the same capabilities as the basic service tier, but includes increased storage and throughput. Premium provide the highest amount of storage and throughput, enabling hyper-scale performance. In addition to higher throughput, premium adds features such as geo-replication, availability zones, private link with private endpoints and content trust.
Use the service tier’s limits for read and write operations and bandwidth as a guide if you expect a high rate of registry operations. These limits affect operations such as listing, deleting, pushing and pulling images and other artifacts. For example, you may experience throttling of pull or push operations when the registry determines the rate of requests exceeds the limits allowed for the service tier. You may see an HTTP 429 error similar to too many requests.
Storage, Throughput & Features By Service Tier
The following table details the storage, throughput and features for each service tier. Note the storage and throughput difference between the basic and standard tier. The standard tier provides ten times more storage, three times more read operations per minute and five times more write operations per minute. It also provides twice as much download and upload speeds.
Resource | Basic | Standard | Premium |
---|---|---|---|
Included storage (GiB) | 10 | 100 | 500 |
Storage limit (TiB) | 20 | 20 | 20 |
Maximum image layer size (GiB) | 200 | 200 | 200 |
Maximum manifest size (MiB) | 4 | 4 | 4 |
ReadOps per minute | 1,000 | 3,000 | 10,000 |
WriteOps per minute | 100 | 500 | 2,000 |
Download bandwidth (Mbps) | 30 | 60 | 100 |
Upload bandwidth (Mbps) | 10 | 20 | 50 |
Webhooks | 2 | 10 | 500 |
Geo-replication | N/A | N/A | Supported |
Availability zones | N/A | N/A | Supported |
Content trust | N/A | N/A | Supported |
Private link with private endpoints | N/A | N/A | Supported |
• Private endpoints | N/A | N/A | 200 |
Public IP network rules | N/A | N/A | 100 |
Service endpoint VNet access | N/A | N/A | Preview |
• Virtual network rules | N/A | N/A | 100 |
Customer-managed keys | N/A | N/A | Supported |
Repository-scoped permissions | Supported | Supported | Supported |
• Tokens | 100 | 500 | 50,000 |
• Scope maps | 100 | 500 | 50,000 |
• Actions | 500 | 500 | 500 |
• Repositories per scope map | 500 | 500 | 500 |
Anonymous pull access | N/A | Preview | Preview |
Geo-replication And Availability Zones For High Availability
Geo-replication and availability zones are both premium features. By configuring geo-replication, you improve the performance of regional deployments with network-close registry access. The container images are replicated across multiple regions while you manage it as a single registry. You also reduce data transfer costs by pulling images from a nearby replicated registry. Furthermore, you also achieve a higher registry resilience if a regional outage occurs.
Let’s suppose you run a containerized web application in an Azure Kubernetes Service (AKS) in West US, East US, Canada Central and West Europe. You also configured the Azure Container Registry (ACR) for geo-replication in the same regions. The web application that runs in a Docker container utilizes the same code and image across all regions. Each regional deployed web application has its own database and configuration. At some point in time, you add a new feature to the web application, start an ACR build action and push the updated image layers. Because only updated image layers are replicated across regions, and each AKS pulls the new container image from the same or nearby region, data transfer costs are reduced to a minimum. You also achieve a higher performance because the nearest region is used to pull the image.
In order to achieve an even higher availability and resiliency, you can choose to enable zone-redundancy. Availability zones are unique physical locations within an Azure region. To ensure resiliency, there’s a minimum of three separate zones in all enabled regions. Each zone has one or more datacenters equipped with independent power, cooling, and networking. When configured for zone redundancy, a registry (or a registry replica in a different region) is replicated across all availability zones in the region, keeping it available if there are datacenter failures.
Use Content Trust For Pushing And Pulling Signed Images
Content trust allows publishers to sign the container images they push to the registry. When consumers configure their Docker client to pull signed images, it will verify both the publisher (source) and the integrity of the image data. As a result, consumers are assured that the signed images were indeed published by the publisher and have not been tampered with after publication. Repositories can contain images with both signed and unsigned tags. For example, you might sign only the myimage:stable
and myimage:latest
images, but not myimage:dev
.
In order to push a trusted image to the Azure Container Registry, you need to enable content trust at the registry level. The user or system also need both the AcrPush and AcrImageSigner roles. After that, you need to enable content trust in the Docker client. The first time you pushed an image with a signed tag, you’re asked to create a passphrase for both a root signing key and a repository signing key. These keys are stored locally on your machine. On each subsequent push to the same repository, you’re asked only for the repository key. Each time you push a trusted image to a new repository, you’re asked to supply a passphrase for a new repository key.
In order to pull a trusted image, you need to enable content trust in the Docker client as well. In this case, the user or system only needs the AcrPull role. When consumers enable content trust, they can only pull images with signed tags. If a client with content trust enabled tries to pull an unsigned tag, the operations fails.
Use Cases Where Azure Container Registry Can By Used
The Azure Container Registry can integrate with all sorts of Azure services and container orchestration systems. Take for example the Azure Kubernetes Service, which is an orchestration service. Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications. You can configure Kubernetes to pull container images from your Azure Container Registry and run them as Docker containers.
You can take this a step further and automatically deploy your container images on code commit. This is done using a CI/CD pipeline and you can use GitHub Actions or Azure DevOps pipelines depending on where your code lives. For example, the steps of a CI/CD pipeline could look something like this:
- Checkout source code after commit on main branch.
- Start a build action and push the container image to Azure Container Registry.
- Deploy the container image to Azure Kubernetes Service.
The Azure Container Registry also integrates well with other container orchestration systems like DC/OS or Docker Swarm.
Explaining The Contents Of A Docker File Used To Build A Container Image
A Docker file is a text file that contains all commands to build an image. It adheres to a specific format and set of instructions as shown in the example below. A Docker image consists of read-only layers each representing a Dockerfile instruction. These layers are stacked and each one is a delta of the changes from the previous layer. When you run the image and generate a container, you add a new layer on top of the underlying layers. This is also called a writeable layer. Changes such as writing new files, modifying existing files, and deleting files, are written to this writable container layer.
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build WORKDIR /src COPY ["ExampleWebApp.csproj", "."] RUN dotnet restore "./ExampleWebApp.csproj" COPY . . WORKDIR "/src/." RUN dotnet build "ExampleWebApp.csproj" -c Release -o /app/build FROM build AS publish RUN dotnet publish "ExampleWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false FROM base AS final WORKDIR /app COPY --from=publish /app/publish . ENTRYPOINT ["dotnet", "ExampleWebApp.dll"]
In the example above, each instruction creates one layer:
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
creates a layer from the aspnet:6.0 Docker image and sets the stage name to ‘base’ so we can access it later using that name.WORKDIR /app
sets the working directory to ‘/app’ for any RUN, CMD, ENTRYPOINT, COPY and ADD instructions that follow it in the Dockerfile.EXPOSE 80
informs Docker that the container listens on port 80.EXPOSE 443
informs Docker that the container listens on port 443.
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
creates a layer from the sdk:6.0 Docker image and sets the stage name to ‘build’.WORKDIR /src
sets the working directory to ‘/src’COPY ["ExampleWebApp.csproj", "."]
adds the WebApplication1.csproj from the current directory to the ‘src/.’ directory.RUN dotnet restore "./ExampleWebApp.csproj"
executes the dotnet restore command to restore dependencies.COPY . .
adds all files from current directory to the ‘src’ working directory.WORKDIR "/src/."
sets the working directory to ‘/src.’RUN dotnet build "ExampleWebApp.csproj" -c Release -o /app/build
executes the dotnet build command which builds the project and all its dependencies.
FROM build AS publish
means the publish stage picks up where the build stage left off.RUN dotnet publish "ExampleWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false
publishes the application and its dependencies to be used for deployment in the ‘app/publish’ directory.
FROM base AS final
means the final stage picks up where the base stage left off.WORKDIR /app
sets the working directory to ‘/app’COPY --from=publish /app/publish .
copies all contents from the ‘/app/publish’ directory in the publish stage to ‘/app’ in the base stage. By doing this, we keep the final image small because only the published files are copied. We do not need the source code or sdk.ENTRYPOINT ["dotnet", "ExampleWebApp.dll"]
runs the web application using the dotnet command.
Container Images Are Made Up Of One Or More Layers
Each instruction in a Dockerfile translates to an image layer.
By sharing common layers between container images, you increase storage efficiency. For example, several images in different repositories might have a common ASP.NET Core base layer, but only one copy of that layer is stored in the registry. By sharing layers, you also optimize layer distribution to nodes and increase pull performance. For example, when a node pulls an image from the registry, and the ASP.NET Core base layer is already present, that same layer isn’t transfered to the node. Instead, it references the layer already existing on the node.
Create Container Registry And Perform A Build Using ACR Quick Task
The Dockerfile explained earlier is used to build a container image for an ASP.NET Web App. Let’s use this example to execute an ACR Build Task in the Azure Container Registry. You can do this by using the commands below. Make sure to update the ACRName variable to the name of your choosing. The commands below will do the following:
- Create a Resource Group in West Europe.
- Create an Azure Container Registry in the Resource Group.
- Start an ACR Build Task which will download the source code from GitHub, build it and store the image as examplewebapp:v1.
An important part to note in the example below is the last part of the GitHub URL: #main:ExampleWebApp
. This will set the execution context to the ExampleWebApp folder in the main branch. If you don’t set the context to this folder, the Docker build will throw an error that it cannot find the ExampleWebApp.csproj file.
$ACRName="<YourRegistry>" $ResourceGroup=$ACRName az group create --resource-group $ResourceGroup --location westeurope az acr create --resource-group $ResourceGroup --name $ACRName --sku Standard --location westeurope az acr build --registry $ACRName --image examplewebapp:v1 https://github.com/wbosland/acr-build-example-aspnet.git#main:ExampleWebApp
As shown in the ACR Build output below, ACR Tasks displays the dependencies discovered. This enables ACR Tasks to automate image builds on base image updates. For example, when a base image is updated with OS or framework patches.
Sending context to registry: wboslandcontainerregistry... Queued a build with ID: cbc Waiting for an agent... 2024/03/03 18:29:53 Downloading source code... 2024/03/03 18:29:55 Finished downloading source code 2024/03/03 18:29:56 Using acb_vol_1663ba0e-5359-46ef-a0b5-61d977fb71d0 as the home volume 2024/03/03 18:29:56 Setting up Docker configuration... 2024/03/03 18:29:57 Successfully set up Docker configuration 2024/03/03 18:29:57 Logging in to registry: wboslandcontainerregistry.azurecr.io 2024/03/03 18:29:57 Successfully logged into wboslandcontainerregistry.azurecr.io 2024/03/03 18:29:57 Executing step ID: build. Timeout(sec): 28800, Working directory: 'ExampleWebApp', Network: '' 2024/03/03 18:29:57 Scanning for dependencies... 2024/03/03 18:29:58 Successfully scanned dependencies 2024/03/03 18:29:58 Launching container with name: build Sending build context to Docker daemon 8.215MB Step 1/17 : FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base 6.0: Pulling from dotnet/aspnet 5d0aeceef7ee: Pulling fs layer 7c2bfda75264: Pulling fs layer 950196e58fe3: Pulling fs layer ecf3c05ee2f6: Pulling fs layer 819f3b5e3ba4: Pulling fs layer ecf3c05ee2f6: Waiting 819f3b5e3ba4: Waiting 7c2bfda75264: Verifying Checksum 7c2bfda75264: Download complete 5d0aeceef7ee: Verifying Checksum 5d0aeceef7ee: Download complete 950196e58fe3: Verifying Checksum 950196e58fe3: Download complete 5d0aeceef7ee: Pull complete 7c2bfda75264: Pull complete 950196e58fe3: Pull complete ecf3c05ee2f6: Verifying Checksum ecf3c05ee2f6: Download complete ecf3c05ee2f6: Pull complete 819f3b5e3ba4: Verifying Checksum 819f3b5e3ba4: Download complete 819f3b5e3ba4: Pull complete Digest: sha256:894c9f49ae9a72b64e61ef6071a33b6b616d0cf48ef25c83c4cf26d185f37565 Status: Downloaded newer image for mcr.microsoft.com/dotnet/aspnet:6.0 ---> 9dace3b3a992 Step 2/17 : WORKDIR /app ---> Running in b91968d8a784 Removing intermediate container b91968d8a784 ---> edfcc9b13bef Step 3/17 : EXPOSE 80 ---> Running in 8c8a6e40f6fb Removing intermediate container 8c8a6e40f6fb ---> 172b0820a320 Step 4/17 : EXPOSE 443 ---> Running in 8bc7acc3f184 Removing intermediate container 8bc7acc3f184 ---> 328a2cff4919 Step 5/17 : FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 6.0: Pulling from dotnet/sdk 5d0aeceef7ee: Already exists 7c2bfda75264: Already exists 950196e58fe3: Already exists ecf3c05ee2f6: Already exists 819f3b5e3ba4: Already exists 19984358397d: Pulling fs layer d99f9f96f040: Pulling fs layer d6d23fc1b8fc: Pulling fs layer d6d23fc1b8fc: Verifying Checksum d6d23fc1b8fc: Download complete 19984358397d: Verifying Checksum 19984358397d: Download complete 19984358397d: Pull complete d99f9f96f040: Verifying Checksum d99f9f96f040: Download complete d99f9f96f040: Pull complete d6d23fc1b8fc: Pull complete Digest: sha256:fdac9ba57a38ffaa6494b93de33983644c44d9e491e4e312f35ddf926c55a073 Status: Downloaded newer image for mcr.microsoft.com/dotnet/sdk:6.0 ---> 694fe26693f8 Step 6/17 : WORKDIR /src ---> Running in 91a6c2be5a25 Removing intermediate container 91a6c2be5a25 ---> 246e83d8f6bc Step 7/17 : COPY ["ExampleWebApp.csproj", "."] ---> 1103b4fcbd66 Step 8/17 : RUN dotnet restore "./ExampleWebApp.csproj" ---> Running in 8d5e652589f1 Determining projects to restore... Restored /src/ExampleWebApp.csproj (in 783 ms). Removing intermediate container 8d5e652589f1 ---> c541a9129506 Step 9/17 : COPY . . ---> 9836918eea96 Step 10/17 : WORKDIR "/src/." ---> Running in a1cecf54a574 Removing intermediate container a1cecf54a574 ---> 8c6ced95149c Step 11/17 : RUN dotnet build "ExampleWebApp.csproj" -c Release -o /app/build ---> Running in 08a6ce358f79 MSBuild version 17.3.2+561848881 for .NET Determining projects to restore... All projects are up-to-date for restore. ExampleWebApp -> /app/build/ExampleWebApp.dll Build succeeded. 0 Warning(s) 0 Error(s) Time Elapsed 00:00:05.98 Removing intermediate container 08a6ce358f79 ---> 67818c8f57b0 Step 12/17 : FROM build AS publish ---> 67818c8f57b0 Step 13/17 : RUN dotnet publish "ExampleWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false ---> Running in d360bde5bce2 MSBuild version 17.3.2+561848881 for .NET Determining projects to restore... All projects are up-to-date for restore. ExampleWebApp -> /src/bin/Release/net6.0/ExampleWebApp.dll ExampleWebApp -> /app/publish/ Removing intermediate container d360bde5bce2 ---> a25f2c422b85 Step 14/17 : FROM base AS final ---> 328a2cff4919 Step 15/17 : WORKDIR /app ---> Running in b833e85c05b1 Removing intermediate container b833e85c05b1 ---> e408ee36c66d Step 16/17 : COPY --from=publish /app/publish . ---> ed2ec5db8ee7 Step 17/17 : ENTRYPOINT ["dotnet", "ExampleWebApp.dll"] ---> Running in c6c45caaaea8 Removing intermediate container c6c45caaaea8 ---> 8656ed9598dd Successfully built 8656ed9598dd Successfully tagged wboslandcontainerregistry.azurecr.io/examplewebapp:v1 2024/03/03 18:30:40 Successfully executed container: build 2024/03/03 18:30:40 Executing step ID: push. Timeout(sec): 3600, Working directory: 'ExampleWebApp', Network: '' 2024/03/03 18:30:40 Pushing image: wboslandcontainerregistry.azurecr.io/examplewebapp:v1, attempt 1 The push refers to repository [wboslandcontainerregistry.azurecr.io/examplewebapp] 0021d47298f0: Preparing 4e42b658b6d7: Preparing 407a0f7a925f: Preparing 5bb6a06c6676: Preparing 76049aadf39b: Preparing a54d0098e057: Preparing 0baf2321956a: Preparing a54d0098e057: Waiting 0baf2321956a: Waiting 4e42b658b6d7: Pushed 0021d47298f0: Pushed 407a0f7a925f: Pushed a54d0098e057: Pushed 76049aadf39b: Pushed 5bb6a06c6676: Pushed 0baf2321956a: Pushed v1: digest: sha256:947575bca39c6e683768bc281cd5d38fd2b1d73e1837d3a28a43b50f442d3ae6 size: 1788 2024/03/03 18:31:01 Successfully pushed image: wboslandcontainerregistry.azurecr.io/examplewebapp:v1 2024/03/03 18:31:01 Step ID: build marked as successful (elapsed time in seconds: 42.337604) 2024/03/03 18:31:01 Populating digests for step ID: build... 2024/03/03 18:31:03 Successfully populated digests for step ID: build 2024/03/03 18:31:03 Step ID: push marked as successful (elapsed time in seconds: 21.285054) 2024/03/03 18:31:03 The following dependencies were found: 2024/03/03 18:31:03 - image: registry: wboslandcontainerregistry.azurecr.io repository: examplewebapp tag: v1 digest: sha256:947575bca39c6e683768bc281cd5d38fd2b1d73e1837d3a28a43b50f442d3ae6 runtime-dependency: registry: mcr.microsoft.com repository: dotnet/aspnet tag: "6.0" digest: sha256:894c9f49ae9a72b64e61ef6071a33b6b616d0cf48ef25c83c4cf26d185f37565 buildtime-dependency: - registry: mcr.microsoft.com repository: dotnet/sdk tag: "6.0" digest: sha256:fdac9ba57a38ffaa6494b93de33983644c44d9e491e4e312f35ddf926c55a073 git: git-head-revision: 30f09de00045a6de53e13def46ca5697ee2489f0 Run ID: cbc was successful after 1m10s
Automate Container Image Build On Code Commit Using ACR Tasks
In order to automate a container image build on code commit in GitHub, you need a Personal Access Token (PAT). Since I used my own GitHub repository, I was able to create one myself. If you want, you could clone the example code to your own GitHub repository and create the PAT. Or, you could ask the owner of a repository to create a PAT for you. Make sure to select the correct scopes so that ACR Tasks can access the repository. For public GitHub repositories, the scopes repo:status
and public_repo
are sufficient. For private GitHub repositories, ACR Tasks needs full repo control.
Create the ACR Task in the container registry you created in the previous chapter using the following PowerShell script:
$ACRName="<YourRegistry>" $GitUser="<YourUsername>" $GitPAT="<YourPAT>" az acr task create ` --registry $ACRName ` --name task-example-web-app ` --image "examplewebapp:{{.Run.ID}}" ` --context https://github.com/$GitUser/acr-build-example-aspnet.git#main:ExampleWebApp ` --file Dockerfile ` --git-access-token $GitPAT
After creation, you can test the task manually using the following command:
az acr task run --registry $ACRName --name task-example-web-app
And after you committed a change to the repository, run the following command to check if the ACR Task has ran:
az acr task list-runs --registry $ACRName --output table
As shown below, the task has been triggered both manually and by a commit to the GitHub repository.
RUN ID TASK PLATFORM STATUS TRIGGER STARTED DURATION -------- -------------------- ---------- --------- --------- -------------------- ---------- cbe task-example-web-app linux Succeeded Commit 2024-03-20T12:09:31Z 00:01:04 cbd task-example-web-app linux Succeeded Manual 2024-03-20T11:25:15Z 00:01:10