Skip to main content

Theoretical Foundation: Create and manage an Azure Container Registry


1. Initial Intuition​

Imagine you've developed an application and need to distribute it to multiple servers, test environments, and production. Instead of copying code and configuring each server individually, you package everything into a container: a self-contained unit that includes the code, dependencies, libraries, and configurations needed to run the application. This container is defined by an image, which works like the "mold" from which any number of containers can be instantiated.

Now you need a place to store and distribute these images. Azure Container Registry (ACR) is exactly that: a private and managed repository for container images in Azure. It's your organization's Docker Hub, but private, secure, and integrated with the Azure ecosystem.

The most accurate analogy is a private technical library: instead of using the public library (Docker Hub), you maintain your own books (images) in a private library where you control who can read and who can add new titles.


2. Context​

ACR occupies a central position in the development and delivery flow of container-based applications:

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Without a private registry, options are to use public Docker Hub (no access control, no isolation, images visible to all) or manually manage image distribution. ACR solves this by offering private storage, integrated authentication with Entra ID, and lifecycle management tools.


3. Concept Construction​

3.1 ACR Structure: Registry, Repository, Tag​

The organizational hierarchy within ACR has three levels:

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular
  • Registry: the service itself, with a globally unique name in the format <name>.azurecr.io
  • Repository: a set of related images (usually an application or service). Within a registry, you can have multiple repositories.
  • Tag: the version of an image within a repository. The latest tag is convention, but has no special behavior; it's a tag like any other.

An image is referenced by its full path: myacr.azurecr.io/api-gateway:v1.2.3

3.2 ACR SKUs​

There are three service tiers:

FeatureBasicStandardPremium
Included storage10 GB100 GB500 GB
Image throughputLowMediumHigh
Geo-replicationNoNoYes
Private LinkNoNoYes
Customer-managed keysNoNoYes
Content Trust (signing)NoNoYes
Webhooks210500
Use casesDev/TestStandard productionEnterprise, multi-region

For AZ-104, the focus is on understanding when to use each tier. Standard is most common for production environments. Premium is necessary when you need geo-replication (images available in multiple regions with low latency) or access via Private Link (private network, without public exposure).

3.3 ACR Authentication​

This is the area of greatest importance for AZ-104. There are three ways to authenticate to ACR:

1. Admin Account

ACR has an optional administrator account with a credential pair (username + 2 passwords). It's simple but less secure, as credentials are shared and not linked to a specific identity.

# Enable admin account
az acr update --name myacr --resource-group rg-containers --admin-enabled true

# Get admin credentials
az acr credential show --name myacr --resource-group rg-containers

2. Identity (Entra ID + RBAC)

The recommended approach. Any Entra ID principal (user, service principal, managed identity) can have RBAC roles assigned to ACR:

ACR RoleWhat it allows
AcrPullPull images (read)
AcrPushPush and pull images
AcrDeleteDelete images
AcrImageSignerSign images (Content Trust)
OwnerManage registry and all permissions
ContributorManage registry but not permissions
ReaderView resources but not images

3. Repository-scoped token

A token with scope limited to one or more specific repositories and with granular permissions (pull, push, delete). Useful for automations that should have access only to part of the registry.

3.4 ACR Tasks: Build and Automation​

ACR has a built-in feature called ACR Tasks that allows executing image builds directly in Azure, without needing a local build agent or separate pipeline for simple operations:

Task TypeDescription
Quick taskSingle build via az acr build, image goes directly to registry
Automatic triggerAutomatic build when committing to code repository
Scheduled taskBuild on schedule (cron) for periodic base image updates
Multi-step taskSequence of steps: build, test, push

4. Structural View​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

5. Practical Operation​

Image Lifecycle in ACR​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

Important and Non-Obvious Behaviors​

Tag is mutable, digest is immutable: a tag like latest can be overwritten by a new push. But each image has an digest (SHA-256 hash) that immutably identifies exactly that version. For production environments, reference images by digest (myacr.azurecr.io/api:sha256:abc123...) instead of tag to ensure reproducibility.

Images are composed of layers: each Dockerfile instruction generates a layer. ACR stores layers in deduplicated form: if two images share the same base layers, they are stored only once. This is important for calculating real storage costs.

ACR does not run containers: it only stores and distributes images. Execution is the responsibility of AKS, ACI, App Service, or any Docker host.

Image pulls charge for egress traffic: image pulls from ACR to outside the Azure region incur egress costs. For multi-region workloads, consider geo-replication (Premium) to have the registry in the same region as the workload.


6. Implementation Methods​

6.1 Azure Portal​

When to use: initial creation, visual verification of repositories and tags, webhook and replication configuration.

Path: Create a resource > Containers > Container Registry

Fields at creation:

  • Registry name: globally unique name (alphanumeric only, 5-50 characters)
  • SKU: Basic, Standard or Premium
  • Admin user: enable or disable administrator account

6.2 Azure CLI​

Create ACR:

az acr create \
--name myacr \
--resource-group rg-containers \
--sku Standard \
--location brazilsouth \
--admin-enabled false

Authenticate with Entra ID for image operations:

# Login using Entra ID credentials (for local dev)
az acr login --name myacr

This gets a temporary token and configures local Docker to use ACR. Valid for 3 hours.

Build image directly in ACR (without local Docker):

# Build Dockerfile in current directory and push directly to ACR
az acr build \
--registry myacr \
--image api-gateway:v1.2.3 \
--file Dockerfile \
.

Image operations:

# List repositories
az acr repository list --name myacr --output table

# List tags of a repository
az acr repository show-tags \
--name myacr \
--repository api-gateway \
--orderby time_desc \
--output table

# View details of a specific image (including digest)
az acr repository show-manifests \
--name myacr \
--repository api-gateway \
--query "[].{Digest:digest, Tags:tags, Created:timestamp}" \
--output table

# Delete a specific tag
az acr repository delete \
--name myacr \
--image api-gateway:v1.2.2 \
--yes

# Delete an entire repository
az acr repository delete \
--name myacr \
--repository api-gateway-old \
--yes

Assign AcrPull role to a service:

# ACR ID
ACR_ID=$(az acr show --name myacr --resource-group rg-containers --query id -o tsv)

# Assign AcrPull to a service principal
az role assignment create \
--assignee <service-principal-object-id> \
--role AcrPull \
--scope $ACR_ID

# Assign AcrPull to an AKS cluster (Managed Identity)
AKS_KUBELET_ID=$(az aks show \
--name my-aks-cluster \
--resource-group rg-aks \
--query identityProfile.kubeletidentity.objectId -o tsv)

az role assignment create \
--assignee $AKS_KUBELET_ID \
--role AcrPull \
--scope $ACR_ID

Configure retention policy (clean up untagged images):

az acr config retention update \
--registry myacr \
--status enabled \
--days 30 \
--type UntaggedManifests

Geo-replication (Premium only):

az acr replication create \
--registry myacr \
--location eastus

6.3 PowerShell​

# Create ACR
New-AzContainerRegistry `
-ResourceGroupName "rg-containers" `
-Name "myacr" `
-Sku "Standard" `
-Location "brazilsouth" `
-EnableAdminUser:$false

# Get ACR ID
$acr = Get-AzContainerRegistry -Name "myacr" -ResourceGroupName "rg-containers"

# Assign AcrPull to a principal
New-AzRoleAssignment `
-ObjectId "<principal-object-id>" `
-RoleDefinitionName "AcrPull" `
-Scope $acr.Id

# List repositories
Get-AzContainerRegistryRepository -RegistryName "myacr"

6.4 Bicep​

param location string = resourceGroup().location
param acrName string = 'myacr'

resource acr 'Microsoft.ContainerRegistry/registries@2023-07-01' = {
name: acrName
location: location
sku: {
name: 'Standard'
}
properties: {
adminUserEnabled: false
policies: {
retentionPolicy: {
status: 'enabled'
days: 30
}
}
}
}

// Give AcrPull to an AKS Managed Identity
resource acrPullAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(acr.id, aksIdentityObjectId, 'AcrPull')
scope: acr
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'7f951dda-4ed3-4680-a7ca-43fe172d538d' // AcrPull role definition ID
)
principalId: aksIdentityObjectId
principalType: 'ServicePrincipal'
}
}

output acrLoginServer string = acr.properties.loginServer

7. Control and Security​

Disable Public Access (Premium SKU)​

With ACR Premium, you can disable public access and use only Private Link:

# Disable public access
az acr update \
--name myacr \
--resource-group rg-containers \
--public-network-enabled false

# Create Private Endpoint for ACR
az network private-endpoint create \
--name pe-acr-myacr \
--resource-group rg-networking \
--vnet-name vnet-production \
--subnet subnet-private-endpoints \
--private-connection-resource-id $(az acr show --name myacr --query id -o tsv) \
--group-id registry \
--connection-name conn-acr-myacr

Microsoft Defender for Containers​

Microsoft Defender for Containers can be enabled to scan images in ACR for known vulnerabilities (CVEs) in operating system packages and application dependencies:

az security pricing create \
--name ContainerRegistry \
--tier Standard

When enabled, each image push triggers an automatic scan, and the result is visible in Microsoft Defender for Cloud with details about each vulnerability found and remediation recommendations.

Content Trust (Image Signing)​

ACR Premium supports Content Trust based on Notary: images are digitally signed, and clients can be configured to only run images with valid signatures. This prevents execution of images that were not produced by the official pipeline.


8. Decision Making​

Which SKU to choose?​

SituationSKUReason
Development environment, testingBasicMinimal cost, sufficient features
Single application production, single regionStandardAdequate throughput, reasonable cost
Multi-region production, high availabilityPremiumGeo-replication for low latency
Compliance, mandatory private networkPremiumPrivate Link available only in Premium
Image signing (Content Trust)PremiumPremium-exclusive feature

How to authenticate CI/CD pipelines to ACR?​

ScenarioApproachReason
Azure DevOps with Service ConnectionService Principal + AcrPushNative integration, auditable
GitHub ActionsService Principal or Workload Identity FederationNo secret to manage with WIF
Azure Pipelines with Managed IdentityAgent Managed Identity + AcrPushNo credentials to rotate
Developer local buildaz acr login (temporary token)Token expires in 3h, no persistent credential
Automation script on Azure VMVM Managed Identity + AcrPull/PushNo credential to manage
External system access without Entra IDRepository-scoped tokenMinimal scope, can be revoked

9. Best Practices​

Never use the admin account in production: the admin account shares credentials among everyone who uses it, without traceability of who did what. Use RBAC with specific identities for each workload.

Apply the principle of least privilege: CI pipelines that only need to push receive AcrPush. AKS clusters that only need to pull receive AcrPull. No one receives Owner or Contributor on ACR unless they need to manage the registry itself.

Use semantic and immutable tags: besides the latest tag (for convenience), use semantic versioned tags like v1.2.3 and ideally also the code commit hash (sha-a1b2c3d). For references in production Kubernetes manifests, use the digest (sha256:...) to ensure the container is exactly what was tested.

Configure retention policies: untagged images accumulate quickly in CI pipelines that do multiple builds per day. Configure retention to remove untagged images after 30 days and old versions after a defined number of versions.

Implement vulnerability scanning: enable Microsoft Defender for Containers and configure alerts for critical vulnerabilities. Include scan results in the CI pipeline as a quality gate (don't promote images with critical vulnerabilities).


10. Common Mistakes​

Using admin account in pipelines

The admin account is enabled "temporarily" for a test and never disabled. Pipelines start using these credentials. Over time, credentials are shared in multiple places, no one knows who has access, and password rotation becomes a traumatic event. Use Service Principals or Managed Identities from the start.

Not configuring retention policy and running out of storage

A CI pipeline that does 20 builds per day produces ~7,300 images per year, each potentially having GB of data. Without retention policy, storage grows indefinitely and costs increase. Configure retention for untagged images right after creating the registry.

Giving AcrPush to workloads that only need AcrPull

An AKS cluster receives AcrPush "to avoid problems". This means if the cluster is compromised, an attacker can push malicious images to the registry. AKS only needs AcrPull to pull images. Never give excessive permissions.

Confusing az acr login with long-term authentication

The az acr login obtains a temporary token valid for 3 hours that configures local Docker. In CI pipelines, don't use az acr login with service principal manually. Use the tool's native authentication mechanism (Docker login with --password-stdin using the service principal token, or Azure DevOps native task).

Referencing latest in production

latest is a mutable tag. If you deploy minhacr.azurecr.io/api:latest and someone pushes a new version with bugs, the next container restart may pull the buggy version. In production, always reference specific versions or digests.


11. Operation and Maintenance​

Monitor Usage and Size​

# View total size used in the registry
az acr show-usage --name minhacr --resource-group rg-containers --output table

# List images by size
az acr repository show-manifests \
--name minhacr \
--repository api-gateway \
--query "[].{Digest:digest, SizeBytes:imageSize, Tags:tags, Created:timestamp}" \
--output table

Manual Cleanup of Old Images​

# Delete specific tags
az acr repository delete \
--name minhacr \
--image api-gateway:v1.0.0 \
--yes

# Purge old untagged images (more than 30 days, untagged)
az acr run \
--cmd "acr purge --filter 'api-gateway:.*' --untagged --ago 30d" \
--registry minhacr \
/dev/null

The az acr run command with acr purge executes a task directly on ACR for cleanup without needing local Docker.

Important Limits​

ItemBasicStandardPremium
Included storage10 GB100 GB500 GB
Layer reads/day1,00010,00050,000
Layer writes/day2005001,000
Webhooks210500
Geo-replications--Unlimited

12. Integration and Automation​

Complete Pipeline: Code to AKS​

100%
Scroll para zoom Β· Arraste para mover Β· πŸ“± Pinch para zoom no celular

ACR Tasks for Automated Build​

Configure an ACR Task that automatically rebuilds when code changes in the repository:

az acr task create \
--registry minhacr \
--name task-build-api-gateway \
--image api-gateway:{{.Run.ID}} \
--arg BUILD_VERSION={{.Run.ID}} \
--context https://github.com/minhaorg/meu-repo.git#refs/heads/main \
--file Dockerfile \
--git-access-token <github-pat>

When there's a commit on the main branch, ACR automatically builds and pushes the image with the run ID as the tag.

Azure DevOps Integration​

# azure-pipelines.yml
stages:
- stage: Build
jobs:
- job: BuildAndPush
pool:
vmImage: ubuntu-latest
steps:
- task: AzureCLI@2
inputs:
azureSubscription: 'my-azure-service-connection'
scriptType: bash
scriptLocation: inlineScript
inlineScript: |
az acr build \
--registry minhacr \
--image api-gateway:$(Build.BuildId) \
--image api-gateway:latest \
.

Using az acr build directly from the pipeline, there's no need to install Docker on the agent: the build happens in the ACR context.


13. Final Summary​

Essential points:

  • ACR is a managed private registry for container images, with native integration to the Azure ecosystem.
  • The structure is: Registry > Repository > Tag/Digest.
  • Three SKUs: Basic (dev/test), Standard (standard production), Premium (multi-region, Private Link, Content Trust).
  • Recommended authentication is via RBAC with Entra ID (Managed Identity or Service Principal). The admin account should be avoided in production.

Critical differences:

  • Tag is mutable (can be overwritten). Digest (SHA-256) is immutable and identifies exactly one image version.
  • AcrPull (read-only for images) vs. AcrPush (write and read) vs. Contributor (manages the registry, not necessarily image access via Docker).
  • Az acr login obtains a temporary 3-hour token for local use. Not for long-running pipelines.
  • ACR Tasks execute builds on Azure without needing local Docker or separate build agent.

What needs to be remembered:

  • Never use the admin account in automations or production. Use RBAC with dedicated identities.
  • Give AcrPull to AKS, AcrPush to CI pipeline, never more than necessary.
  • Configure retention policy to avoid untagged image accumulation and uncontrolled cost growth.
  • Private Link and geo-replication are Premium SKU exclusive features.
  • For builds without local Docker: az acr build --registry minhacr --image name:tag .
  • AcrPull assignment to AKS is done on the kubelet identity (node pool identity), not on the cluster control plane identity.