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:
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:
- 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
latesttag 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:
| Feature | Basic | Standard | Premium |
|---|---|---|---|
| Included storage | 10 GB | 100 GB | 500 GB |
| Image throughput | Low | Medium | High |
| Geo-replication | No | No | Yes |
| Private Link | No | No | Yes |
| Customer-managed keys | No | No | Yes |
| Content Trust (signing) | No | No | Yes |
| Webhooks | 2 | 10 | 500 |
| Use cases | Dev/Test | Standard production | Enterprise, 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 Role | What it allows |
|---|---|
| AcrPull | Pull images (read) |
| AcrPush | Push and pull images |
| AcrDelete | Delete images |
| AcrImageSigner | Sign images (Content Trust) |
| Owner | Manage registry and all permissions |
| Contributor | Manage registry but not permissions |
| Reader | View 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 Type | Description |
|---|---|
| Quick task | Single build via az acr build, image goes directly to registry |
| Automatic trigger | Automatic build when committing to code repository |
| Scheduled task | Build on schedule (cron) for periodic base image updates |
| Multi-step task | Sequence of steps: build, test, push |
4. Structural Viewβ
5. Practical Operationβ
Image Lifecycle in ACRβ
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?β
| Situation | SKU | Reason |
|---|---|---|
| Development environment, testing | Basic | Minimal cost, sufficient features |
| Single application production, single region | Standard | Adequate throughput, reasonable cost |
| Multi-region production, high availability | Premium | Geo-replication for low latency |
| Compliance, mandatory private network | Premium | Private Link available only in Premium |
| Image signing (Content Trust) | Premium | Premium-exclusive feature |
How to authenticate CI/CD pipelines to ACR?β
| Scenario | Approach | Reason |
|---|---|---|
| Azure DevOps with Service Connection | Service Principal + AcrPush | Native integration, auditable |
| GitHub Actions | Service Principal or Workload Identity Federation | No secret to manage with WIF |
| Azure Pipelines with Managed Identity | Agent Managed Identity + AcrPush | No credentials to rotate |
| Developer local build | az acr login (temporary token) | Token expires in 3h, no persistent credential |
| Automation script on Azure VM | VM Managed Identity + AcrPull/Push | No credential to manage |
| External system access without Entra ID | Repository-scoped token | Minimal 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β
| Item | Basic | Standard | Premium |
|---|---|---|---|
| Included storage | 10 GB | 100 GB | 500 GB |
| Layer reads/day | 1,000 | 10,000 | 50,000 |
| Layer writes/day | 200 | 500 | 1,000 |
| Webhooks | 2 | 10 | 500 |
| Geo-replications | - | - | Unlimited |
12. Integration and Automationβ
Complete Pipeline: Code to AKSβ
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
AcrPullto AKS,AcrPushto 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.