Skip to content

🐳 Azure Container Registry (ACR) – Complete End-to-End Tutorial

πŸ“‹ Table of Contents

  1. Overview
  2. ACR Architecture
  3. Service Tiers
  4. Prerequisites
  5. Creating ACR via Azure Portal
  6. Creating ACR via Azure CLI
  7. Creating ACR via Terraform
  8. Authentication Methods
  9. Push & Pull Images
  10. ACR Tasks
  11. Geo-Replication
  12. Security & Access Control
  13. Integration with AKS
  14. Best Practices

πŸ“– Overview

Azure Container Registry (ACR) is a managed Docker registry service based on the open-source Docker Registry 2.0. It allows you to store and manage container images and artifacts in a private registry for all types of container deployments.

Key Features

  • Private Docker Registry: Store container images securely
  • Geo-Replication: Replicate images across multiple Azure regions
  • ACR Tasks: Build, test, and patch container images in Azure
  • Security: Integration with Azure AD, RBAC, and private endpoints
  • Webhook Support: Trigger events on image push/delete
  • Content Trust: Sign and verify images
  • Vulnerability Scanning: Integrated security scanning

Use Cases

  • Store images for Azure Kubernetes Service (AKS)
  • CI/CD pipelines with Azure DevOps, GitHub Actions
  • Multi-region deployments with geo-replication
  • Artifact storage for Helm charts, OCI artifacts

πŸ—οΈ ACR Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Azure Container Registry                  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                                                               β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”      β”‚
β”‚  β”‚  Repository  β”‚  β”‚  Repository  β”‚  β”‚  Repository  β”‚      β”‚
β”‚  β”‚   app-web    β”‚  β”‚   app-api    β”‚  β”‚   app-db     β”‚      β”‚
β”‚  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€      β”‚
β”‚  β”‚ v1.0.0       β”‚  β”‚ v2.1.0       β”‚  β”‚ latest       β”‚      β”‚
β”‚  β”‚ v1.0.1       β”‚  β”‚ v2.1.1       β”‚  β”‚ v5.7         β”‚      β”‚
β”‚  β”‚ latest       β”‚  β”‚ latest       β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜      β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜                         β”‚
β”‚                                                               β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚                    Security & Access                         β”‚
β”‚  β€’ Azure AD Authentication                                   β”‚
β”‚  β€’ Service Principal / Managed Identity                      β”‚
β”‚  β€’ RBAC (Role-Based Access Control)                         β”‚
β”‚  β€’ Private Endpoints / Firewall Rules                        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚                    β”‚                    β”‚
         β–Ό                    β–Ό                    β–Ό
    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”
    β”‚   AKS   β”‚         β”‚  Docker β”‚         β”‚ CI/CD   β”‚
    β”‚ Cluster β”‚         β”‚  Client β”‚         β”‚Pipeline β”‚
    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ’° Service Tiers

Feature Basic Standard Premium
Storage 10 GB 100 GB 500 GB
Throughput Low Medium High
Geo-Replication ❌ ❌ βœ…
Content Trust ❌ ❌ βœ…
Private Link ❌ ❌ βœ…
Webhooks 2 10 500
Use Case Dev/Test Production Enterprise

βœ… Prerequisites

# Install Azure CLI
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash

# Login to Azure
az login

# Set subscription
az account set --subscription "your-subscription-id"

# Install Docker
sudo apt-get update
sudo apt-get install docker.io -y
sudo systemctl start docker
sudo systemctl enable docker

🌐 Creating ACR via Azure Portal

Step 1: Navigate to Container Registries

  1. Go to Azure Portal β†’ Search "Container Registries"
  2. Click + Create

Step 2: Configure Basic Settings

  • Subscription: Select your subscription
  • Resource Group: Create new or select existing
  • Registry Name: myacrregistry (must be globally unique)
  • Location: East US
  • SKU: Standard or Premium

Step 3: Networking (Optional)

  • Public Access: Enable/Disable
  • Private Endpoint: Configure if needed

Step 4: Review + Create

  • Click Create and wait for deployment

πŸ’» Creating ACR via Azure CLI

Basic ACR Creation

# Variables
RESOURCE_GROUP="rg-acr-demo"
LOCATION="eastus"
ACR_NAME="myacrregistry2024"  # Must be globally unique
SKU="Standard"  # Basic, Standard, or Premium

# Create Resource Group
az group create \
  --name $RESOURCE_GROUP \
  --location $LOCATION

# Create ACR
az acr create \
  --resource-group $RESOURCE_GROUP \
  --name $ACR_NAME \
  --sku $SKU \
  --location $LOCATION

# Enable Admin User (for testing only)
az acr update \
  --name $ACR_NAME \
  --admin-enabled true

# Get ACR Login Server
az acr show \
  --name $ACR_NAME \
  --query loginServer \
  --output tsv

Premium ACR with Geo-Replication

# Create Premium ACR
az acr create \
  --resource-group $RESOURCE_GROUP \
  --name $ACR_NAME \
  --sku Premium \
  --location eastus

# Add Geo-Replication
az acr replication create \
  --registry $ACR_NAME \
  --location westus

az acr replication create \
  --registry $ACR_NAME \
  --location westeurope

πŸ”§ Creating ACR via Terraform

Complete Terraform Configuration

1. Provider Configuration (provider.tf)

terraform {
  required_version = ">= 1.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

2. Variables (variables.tf)

variable "resource_group_name" {
  description = "Name of the resource group"
  type        = string
  default     = "rg-acr-terraform"
}

variable "location" {
  description = "Azure region"
  type        = string
  default     = "eastus"
}

variable "acr_name" {
  description = "Name of the Azure Container Registry"
  type        = string
  default     = "myacrtf2024"
}

variable "acr_sku" {
  description = "SKU for ACR (Basic, Standard, Premium)"
  type        = string
  default     = "Standard"
}

variable "admin_enabled" {
  description = "Enable admin user"
  type        = bool
  default     = false
}

variable "geo_replication_locations" {
  description = "List of locations for geo-replication"
  type        = list(string)
  default     = []
}

variable "tags" {
  description = "Tags to apply to resources"
  type        = map(string)
  default = {
    Environment = "Production"
    ManagedBy   = "Terraform"
  }
}

3. Main Configuration (main.tf)

# Resource Group
resource "azurerm_resource_group" "acr_rg" {
  name     = var.resource_group_name
  location = var.location
  tags     = var.tags
}

# Azure Container Registry
resource "azurerm_container_registry" "acr" {
  name                = var.acr_name
  resource_group_name = azurerm_resource_group.acr_rg.name
  location            = azurerm_resource_group.acr_rg.location
  sku                 = var.acr_sku
  admin_enabled       = var.admin_enabled

  # Public network access
  public_network_access_enabled = true

  # Network rule set (Premium only)
  dynamic "network_rule_set" {
    for_each = var.acr_sku == "Premium" ? [1] : []
    content {
      default_action = "Allow"
    }
  }

  # Geo-replication (Premium only)
  dynamic "georeplications" {
    for_each = var.acr_sku == "Premium" ? var.geo_replication_locations : []
    content {
      location                = georeplications.value
      zone_redundancy_enabled = true
      tags                    = var.tags
    }
  }

  tags = var.tags
}

# Diagnostic Settings (Optional)
resource "azurerm_monitor_diagnostic_setting" "acr_diagnostics" {
  name                       = "acr-diagnostics"
  target_resource_id         = azurerm_container_registry.acr.id
  log_analytics_workspace_id = azurerm_log_analytics_workspace.acr_logs.id

  enabled_log {
    category = "ContainerRegistryRepositoryEvents"
  }

  enabled_log {
    category = "ContainerRegistryLoginEvents"
  }

  metric {
    category = "AllMetrics"
    enabled  = true
  }
}

# Log Analytics Workspace
resource "azurerm_log_analytics_workspace" "acr_logs" {
  name                = "${var.acr_name}-logs"
  location            = azurerm_resource_group.acr_rg.location
  resource_group_name = azurerm_resource_group.acr_rg.name
  sku                 = "PerGB2018"
  retention_in_days   = 30
  tags                = var.tags
}

4. Advanced Configuration with RBAC (acr-advanced.tf)

# Service Principal for ACR
data "azurerm_client_config" "current" {}

# User Assigned Managed Identity
resource "azurerm_user_assigned_identity" "acr_identity" {
  name                = "${var.acr_name}-identity"
  resource_group_name = azurerm_resource_group.acr_rg.name
  location            = azurerm_resource_group.acr_rg.location
  tags                = var.tags
}

# Role Assignment - AcrPull
resource "azurerm_role_assignment" "acr_pull" {
  scope                = azurerm_container_registry.acr.id
  role_definition_name = "AcrPull"
  principal_id         = azurerm_user_assigned_identity.acr_identity.principal_id
}

# Role Assignment - AcrPush
resource "azurerm_role_assignment" "acr_push" {
  scope                = azurerm_container_registry.acr.id
  role_definition_name = "AcrPush"
  principal_id         = azurerm_user_assigned_identity.acr_identity.principal_id
}

# Private Endpoint (Premium only)
resource "azurerm_private_endpoint" "acr_pe" {
  count               = var.acr_sku == "Premium" ? 1 : 0
  name                = "${var.acr_name}-pe"
  location            = azurerm_resource_group.acr_rg.location
  resource_group_name = azurerm_resource_group.acr_rg.name
  subnet_id           = azurerm_subnet.acr_subnet[0].id

  private_service_connection {
    name                           = "${var.acr_name}-psc"
    private_connection_resource_id = azurerm_container_registry.acr.id
    subresource_names              = ["registry"]
    is_manual_connection           = false
  }

  tags = var.tags
}

# Virtual Network for Private Endpoint
resource "azurerm_virtual_network" "acr_vnet" {
  count               = var.acr_sku == "Premium" ? 1 : 0
  name                = "${var.acr_name}-vnet"
  address_space       = ["10.0.0.0/16"]
  location            = azurerm_resource_group.acr_rg.location
  resource_group_name = azurerm_resource_group.acr_rg.name
  tags                = var.tags
}

resource "azurerm_subnet" "acr_subnet" {
  count                = var.acr_sku == "Premium" ? 1 : 0
  name                 = "acr-subnet"
  resource_group_name  = azurerm_resource_group.acr_rg.name
  virtual_network_name = azurerm_virtual_network.acr_vnet[0].name
  address_prefixes     = ["10.0.1.0/24"]
}

# Webhook for CI/CD
resource "azurerm_container_registry_webhook" "acr_webhook" {
  name                = "acrwebhook"
  resource_group_name = azurerm_resource_group.acr_rg.name
  registry_name       = azurerm_container_registry.acr.name
  location            = azurerm_resource_group.acr_rg.location

  service_uri = "https://myapp.example.com/webhook"
  status      = "enabled"
  scope       = "myapp:*"
  actions     = ["push", "delete"]

  custom_headers = {
    "Content-Type" = "application/json"
  }
}

5. Outputs (outputs.tf)

output "acr_id" {
  description = "The ID of the Container Registry"
  value       = azurerm_container_registry.acr.id
}

output "acr_login_server" {
  description = "The URL that can be used to log into the container registry"
  value       = azurerm_container_registry.acr.login_server
}

output "acr_admin_username" {
  description = "The Username associated with the Container Registry Admin account"
  value       = var.admin_enabled ? azurerm_container_registry.acr.admin_username : null
  sensitive   = true
}

output "acr_admin_password" {
  description = "The Password associated with the Container Registry Admin account"
  value       = var.admin_enabled ? azurerm_container_registry.acr.admin_password : null
  sensitive   = true
}

output "managed_identity_id" {
  description = "The ID of the User Assigned Managed Identity"
  value       = azurerm_user_assigned_identity.acr_identity.id
}

output "managed_identity_client_id" {
  description = "The Client ID of the User Assigned Managed Identity"
  value       = azurerm_user_assigned_identity.acr_identity.client_id
}

6. Terraform Variables File (terraform.tfvars)

# Basic Configuration
resource_group_name = "rg-acr-production"
location            = "eastus"
acr_name            = "mycompanyacr2024"
acr_sku             = "Premium"
admin_enabled       = false

# Geo-Replication (Premium only)
geo_replication_locations = [
  "westus",
  "westeurope"
]

# Tags
tags = {
  Environment = "Production"
  Project     = "ContainerRegistry"
  ManagedBy   = "Terraform"
  CostCenter  = "Engineering"
}

7. Deploy with Terraform

# Initialize Terraform
terraform init

# Validate configuration
terraform validate

# Plan deployment
terraform plan -out=tfplan

# Apply configuration
terraform apply tfplan

# Get outputs
terraform output acr_login_server
terraform output -json

πŸ” Authentication Methods

1. Azure CLI Authentication

# Login to ACR
az acr login --name $ACR_NAME

# Alternative: Get access token
TOKEN=$(az acr login --name $ACR_NAME --expose-token --output tsv --query accessToken)
echo $TOKEN | docker login $ACR_NAME.azurecr.io --username 00000000-0000-0000-0000-000000000000 --password-stdin

2. Service Principal Authentication

# Create Service Principal
SP_NAME="acr-service-principal"
ACR_REGISTRY_ID=$(az acr show --name $ACR_NAME --query id --output tsv)

SP_PASSWD=$(az ad sp create-for-rbac \
  --name $SP_NAME \
  --scopes $ACR_REGISTRY_ID \
  --role acrpull \
  --query password \
  --output tsv)

SP_APP_ID=$(az ad sp list \
  --display-name $SP_NAME \
  --query [].appId \
  --output tsv)

# Docker login with Service Principal
docker login $ACR_NAME.azurecr.io \
  --username $SP_APP_ID \
  --password $SP_PASSWD

3. Managed Identity Authentication

# Assign AcrPull role to Managed Identity
IDENTITY_ID="/subscriptions/xxx/resourcegroups/xxx/providers/Microsoft.ManagedIdentity/userAssignedIdentities/myidentity"

az role assignment create \
  --assignee $IDENTITY_ID \
  --scope $ACR_REGISTRY_ID \
  --role acrpull
# Get admin credentials
ACR_USERNAME=$(az acr credential show --name $ACR_NAME --query username --output tsv)
ACR_PASSWORD=$(az acr credential show --name $ACR_NAME --query passwords[0].value --output tsv)

# Docker login
docker login $ACR_NAME.azurecr.io \
  --username $ACR_USERNAME \
  --password $ACR_PASSWORD

πŸ“¦ Push & Pull Images

Build and Push Image

# Set variables
ACR_NAME="myacrregistry2024"
IMAGE_NAME="myapp"
IMAGE_TAG="v1.0.0"

# Build Docker image
docker build -t $IMAGE_NAME:$IMAGE_TAG .

# Tag image for ACR
docker tag $IMAGE_NAME:$IMAGE_TAG $ACR_NAME.azurecr.io/$IMAGE_NAME:$IMAGE_TAG

# Push to ACR
docker push $ACR_NAME.azurecr.io/$IMAGE_NAME:$IMAGE_TAG

# List images in ACR
az acr repository list --name $ACR_NAME --output table

# Show tags for a repository
az acr repository show-tags --name $ACR_NAME --repository $IMAGE_NAME --output table

Pull Image from ACR

# Pull image
docker pull $ACR_NAME.azurecr.io/$IMAGE_NAME:$IMAGE_TAG

# Run container
docker run -d -p 8080:80 $ACR_NAME.azurecr.io/$IMAGE_NAME:$IMAGE_TAG

ACR Build (Build in Cloud)

# Build image in ACR without local Docker
az acr build \
  --registry $ACR_NAME \
  --image myapp:v1.0.0 \
  --file Dockerfile \
  .

# Build with build arguments
az acr build \
  --registry $ACR_NAME \
  --image myapp:v1.0.0 \
  --build-arg NODE_VERSION=18 \
  --file Dockerfile \
  .

βš™οΈ ACR Tasks

Quick Task (One-time build)

# Build and push in one command
az acr build \
  --registry $ACR_NAME \
  --image myapp:{{.Run.ID}} \
  --file Dockerfile \
  https://github.com/username/repo.git

Automated Task (Triggered builds)

# Create task triggered by Git commit
az acr task create \
  --registry $ACR_NAME \
  --name buildtask \
  --image myapp:{{.Run.ID}} \
  --context https://github.com/username/repo.git \
  --file Dockerfile \
  --git-access-token $GITHUB_TOKEN

# Create task triggered by base image update
az acr task create \
  --registry $ACR_NAME \
  --name basetask \
  --image myapp:{{.Run.ID}} \
  --context https://github.com/username/repo.git \
  --file Dockerfile \
  --base-image-trigger-enabled true

# Run task manually
az acr task run --registry $ACR_NAME --name buildtask

# List tasks
az acr task list --registry $ACR_NAME --output table

# Show task runs
az acr task list-runs --registry $ACR_NAME --output table

Multi-Step Task

# acr-task.yaml
version: v1.1.0
steps:
  # Build image
  - build: -t {{.Run.Registry}}/myapp:{{.Run.ID}} .

  # Run tests
  - cmd: {{.Run.Registry}}/myapp:{{.Run.ID}}
    env:
      - TEST_MODE=true

  # Push if tests pass
  - push: 
    - {{.Run.Registry}}/myapp:{{.Run.ID}}
    - {{.Run.Registry}}/myapp:latest
# Create multi-step task
az acr task create \
  --registry $ACR_NAME \
  --name multisteptask \
  --context https://github.com/username/repo.git \
  --file acr-task.yaml \
  --git-access-token $GITHUB_TOKEN

🌍 Geo-Replication

Enable Geo-Replication (Premium SKU Required)

# Add replication to West US
az acr replication create \
  --registry $ACR_NAME \
  --location westus

# Add replication to West Europe
az acr replication create \
  --registry $ACR_NAME \
  --location westeurope

# List replications
az acr replication list \
  --registry $ACR_NAME \
  --output table

# Delete replication
az acr replication delete \
  --registry $ACR_NAME \
  --location westus

Terraform Geo-Replication

resource "azurerm_container_registry" "acr" {
  name                = "myacr"
  resource_group_name = azurerm_resource_group.rg.name
  location            = "eastus"
  sku                 = "Premium"

  georeplications {
    location                = "westus"
    zone_redundancy_enabled = true
    tags                    = var.tags
  }

  georeplications {
    location                = "westeurope"
    zone_redundancy_enabled = true
    tags                    = var.tags
  }
}

πŸ”’ Security & Access Control

Network Security

# Disable public access (Premium only)
az acr update \
  --name $ACR_NAME \
  --public-network-enabled false

# Add firewall rule
az acr network-rule add \
  --name $ACR_NAME \
  --ip-address 203.0.113.0/24

# List network rules
az acr network-rule list --name $ACR_NAME

RBAC Roles

# Assign AcrPull role (read-only)
az role assignment create \
  --assignee user@example.com \
  --role AcrPull \
  --scope $ACR_REGISTRY_ID

# Assign AcrPush role (read-write)
az role assignment create \
  --assignee user@example.com \
  --role AcrPush \
  --scope $ACR_REGISTRY_ID

# Assign AcrDelete role
az role assignment create \
  --assignee user@example.com \
  --role AcrDelete \
  --scope $ACR_REGISTRY_ID

Content Trust (Image Signing)

# Enable content trust
export DOCKER_CONTENT_TRUST=1
export DOCKER_CONTENT_TRUST_SERVER=https://$ACR_NAME.azurecr.io

# Push signed image
docker push $ACR_NAME.azurecr.io/myapp:v1.0.0

☸️ Integration with AKS

Attach ACR to AKS

AKS_NAME="myakscluster"
AKS_RG="rg-aks"

# Attach ACR to AKS
az aks update \
  --name $AKS_NAME \
  --resource-group $AKS_RG \
  --attach-acr $ACR_NAME

# Verify integration
az aks check-acr \
  --name $AKS_NAME \
  --resource-group $AKS_RG \
  --acr $ACR_NAME.azurecr.io

Terraform AKS + ACR Integration

# AKS Cluster
resource "azurerm_kubernetes_cluster" "aks" {
  name                = "myakscluster"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name
  dns_prefix          = "myaks"

  default_node_pool {
    name       = "default"
    node_count = 3
    vm_size    = "Standard_D2_v2"
  }

  identity {
    type = "SystemAssigned"
  }
}

# Grant AKS access to ACR
resource "azurerm_role_assignment" "aks_acr" {
  principal_id                     = azurerm_kubernetes_cluster.aks.kubelet_identity[0].object_id
  role_definition_name             = "AcrPull"
  scope                            = azurerm_container_registry.acr.id
  skip_service_principal_aad_check = true
}

Deploy to AKS from ACR

# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myacrregistry2024.azurecr.io/myapp:v1.0.0
        ports:
        - containerPort: 80
# Apply deployment
kubectl apply -f deployment.yaml

# Verify pods
kubectl get pods

🎯 Best Practices

1. Security

  • βœ… Use Managed Identities instead of admin credentials
  • βœ… Enable Azure AD authentication
  • βœ… Use Private Endpoints for production
  • βœ… Enable Content Trust for image signing
  • βœ… Implement RBAC with least privilege
  • βœ… Enable vulnerability scanning
  • βœ… Use firewall rules to restrict access

2. Image Management

  • βœ… Use semantic versioning for tags
  • βœ… Implement image retention policies
  • βœ… Use multi-stage builds to reduce image size
  • βœ… Scan images for vulnerabilities
  • βœ… Tag images with commit SHA for traceability

3. Performance

  • βœ… Use Premium SKU for production workloads
  • βœ… Enable geo-replication for global deployments
  • βœ… Use ACR Tasks for automated builds
  • βœ… Implement caching strategies

4. Cost Optimization

  • βœ… Use appropriate SKU for your needs
  • βœ… Implement image cleanup policies
  • βœ… Monitor storage usage
  • βœ… Delete unused images and tags

5. Monitoring

  • βœ… Enable diagnostic logs
  • βœ… Set up alerts for failed pushes/pulls
  • βœ… Monitor storage metrics
  • βœ… Track authentication failures

πŸ“Š Monitoring & Diagnostics

Enable Diagnostics

# Create Log Analytics Workspace
az monitor log-analytics workspace create \
  --resource-group $RESOURCE_GROUP \
  --workspace-name acr-logs

# Get workspace ID
WORKSPACE_ID=$(az monitor log-analytics workspace show \
  --resource-group $RESOURCE_GROUP \
  --workspace-name acr-logs \
  --query id \
  --output tsv)

# Enable diagnostic settings
az monitor diagnostic-settings create \
  --name acr-diagnostics \
  --resource $ACR_REGISTRY_ID \
  --workspace $WORKSPACE_ID \
  --logs '[{"category": "ContainerRegistryRepositoryEvents", "enabled": true}, {"category": "ContainerRegistryLoginEvents", "enabled": true}]' \
  --metrics '[{"category": "AllMetrics", "enabled": true}]'

Query Logs

// Failed login attempts
ContainerRegistryLoginEvents
| where ResultType == "Failed"
| project TimeGenerated, Identity, LoginServer, ResultDescription

// Image push events
ContainerRegistryRepositoryEvents
| where OperationName == "Push"
| project TimeGenerated, Repository, Tag, Identity

🧹 Cleanup & Maintenance

Delete Old Images

# Delete specific tag
az acr repository delete \
  --name $ACR_NAME \
  --image myapp:v1.0.0 \
  --yes

# Delete all tags older than 30 days
az acr repository show-tags \
  --name $ACR_NAME \
  --repository myapp \
  --orderby time_asc \
  --query "[?lastUpdateTime < '$(date -d '30 days ago' -Iseconds)'].name" \
  --output tsv | xargs -I {} az acr repository delete \
  --name $ACR_NAME \
  --image myapp:{} \
  --yes

Retention Policy (Premium only)

# Set retention policy
az acr config retention update \
  --registry $ACR_NAME \
  --status enabled \
  --days 30 \
  --type UntaggedManifests

Terraform Cleanup

# Destroy all resources
terraform destroy -auto-approve

πŸ“š Complete Example: CI/CD with GitHub Actions

GitHub Actions Workflow

# .github/workflows/build-push-acr.yml
name: Build and Push to ACR

on:
  push:
    branches: [ main ]

env:
  ACR_NAME: myacrregistry2024
  IMAGE_NAME: myapp

jobs:
  build-and-push:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Azure Login
      uses: azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS }}

    - name: Build and push image
      run: |
        az acr build \
          --registry ${{ env.ACR_NAME }} \
          --image ${{ env.IMAGE_NAME }}:${{ github.sha }} \
          --image ${{ env.IMAGE_NAME }}:latest \
          --file Dockerfile \
          .

    - name: Deploy to AKS
      uses: azure/k8s-deploy@v4
      with:
        manifests: |
          k8s/deployment.yaml
        images: |
          ${{ env.ACR_NAME }}.azurecr.io/${{ env.IMAGE_NAME }}:${{ github.sha }}

πŸŽ“ Summary

Azure Container Registry provides a robust, secure, and scalable solution for managing container images. Key takeaways:

  • Choose the right SKU: Basic for dev/test, Premium for production
  • Security first: Use Managed Identities and Private Endpoints
  • Automate: Leverage ACR Tasks for CI/CD
  • Scale globally: Use geo-replication for multi-region deployments
  • Monitor: Enable diagnostics and set up alerts
  • Integrate: Seamlessly connect with AKS and other Azure services

With Terraform, you can manage ACR infrastructure as code, ensuring consistency and repeatability across environments.