Background
Sections
IntroductionFoundations1. Resource Hierarchy2. Resource Manager3. Identity and RBAC4. Regions and Availability5. Naming and TaggingVirtual Machines1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetVirtual Network1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetBlob Storage1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure SQL Database1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Kubernetes Service1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Container Registry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetMicrosoft Entra ID1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure RBAC1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Functions1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAPI Management1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure App Configuration1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Machine Learning1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure Monitor1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and CheatsheetAzure AI Foundry1. What and Why2. Core Concepts3. Architecture4. Getting Started5. Deployment6. Integrations7. Production8. Interview Questions9. Glossary and Cheatsheet

4. Getting Started

6 min read

The smallest thing that proves a registry works: create one, get an image into it, get the image back out, then delete everything. Hard-coded names, no variables, no pipeline. Anything that looks production-shaped belongs in Deployment, not here.

Prerequisites: the az CLI, logged in (az login), with rights to create a resource group. Docker is optional — one of ACR's better features is that az acr build builds in Azure, so you can do this whole page from a machine with no container runtime at all.

Pick a name now. Registry names are globally unique, 5–50 characters, alphanumeric only — no hyphens. Every example below uses acrdemo<something-unique>; substitute your own and keep it consistent.

The task

Create a registry, push a one-line image called hello:v1, pull it back by digest, and tear down.

1. Azure Portal

  • Search Container registriesCreate.
  • On Basics: new resource group rg-acr-demo, registry name acrdemo<unique>, location uksouth, Pricing plan: Basic.
  • Leave every other tab at its default. Do not enable the admin user. Review + create.
  • Open the registry → Repositories blade. It's empty — a registry with no images is normal and costs the tier's daily rate regardless.
  • Note the Login server on the Overview blade: acrdemo<unique>.azurecr.io. That string is the only thing any client needs.

The portal is the fastest way to see the shape of the resource and the worst way to create one twice.

2. Azure CLI

# --- variables for this session only ---
RG=rg-acr-demo
LOC=uksouth
ACR=acrdemo$RANDOM        # must be globally unique, alphanumeric only

# 1. throwaway resource group — the whole point is that one delete cleans up everything
az group create -n $RG -l $LOC

# 2. the registry. Basic is right for a demo; admin user stays off (it's off by default)
az acr create -n $ACR -g $RG -l $LOC --sku Basic

# 3. build the image IN AZURE — no local Docker daemon required.
#    ACR Tasks uploads the build context, builds, and pushes in one step.
mkdir -p /tmp/acr-demo && cd /tmp/acr-demo
cat > Dockerfile <<'EOF'
FROM mcr.microsoft.com/cbl-mariner/busybox:2.0
CMD ["echo", "hello from ACR"]
EOF

az acr build -r $ACR -t hello:v1 .

# 4. look at what you created
az acr repository list -n $ACR -o table
az acr repository show-tags -n $ACR --repository hello -o table

# 5. get the DIGEST — the only unambiguous name for this image
DIGEST=$(az acr repository show -n $ACR --image hello:v1 --query digest -o tsv)
echo "$ACR.azurecr.io/hello@$DIGEST"

If you do have Docker locally and want to see the push path yourself:

az acr login -n $ACR                     # Entra token exchange; writes a short-lived token to Docker config
docker pull mcr.microsoft.com/cbl-mariner/busybox:2.0
docker tag  mcr.microsoft.com/cbl-mariner/busybox:2.0 $ACR.azurecr.io/hello:v2
docker push $ACR.azurecr.io/hello:v2     # watch it skip layers it already has
docker pull $ACR.azurecr.io/hello@$DIGEST   # pull by digest — reproducible

Run something from the registry, to prove the pull path works end to end:

# ACI can pull from ACR using a system-assigned identity, but for a throwaway demo
# the simplest proof is to have ACR Tasks run the image:
az acr run -r $ACR --cmd "$ACR.azurecr.io/hello:v1" /dev/null

Windows / PowerShell-first shops — ACR is frequently driven from PowerShell alongside AKS and Entra work, so the Az equivalents are worth having:

New-AzResourceGroup -Name rg-acr-demo -Location uksouth
New-AzContainerRegistry -ResourceGroupName rg-acr-demo -Name acrdemo0001 -Sku Basic -Location uksouth
Get-AzContainerRegistryRepository -RegistryName acrdemo0001
Connect-AzContainerRegistry -Name acrdemo0001      # the az acr login equivalent

3. Terraform (minimal)

Deliberately unparameterised. The reusable version is in Deployment.

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

provider "azurerm" {
  features {}
}

resource "azurerm_resource_group" "demo" {
  name     = "rg-acr-demo"
  location = "uksouth"
}

resource "azurerm_container_registry" "demo" {
  name                = "acrdemo0001" # globally unique, alphanumeric only
  resource_group_name = azurerm_resource_group.demo.name
  location            = azurerm_resource_group.demo.location
  sku                 = "Basic"
  admin_enabled       = false # the default, and the correct value — state it explicitly anyway
}

output "login_server" {
  value = azurerm_container_registry.demo.login_server
}
terraform init
terraform plan      # read it. two resources to add, nothing to change or destroy
terraform apply

Three things to notice even in a five-line resource:

  • admin_enabled = false is the default, but writing it makes the intent reviewable and makes a later true show up in a diff.
  • The name has no hyphens. Terraform will happily plan an invalid name and fail at apply.
  • There is no repository resource. Repositories are data, created by pushes. Terraform manages the registry; your pipeline manages its contents. That division holds for the rest of this topic.

[Image Prompt: 2D minimalistic diagram comparing three provisioning paths — Azure Portal, az CLI, and Terraform — all converging on the same Azure Container Registry resource, with a separate arrow showing images arriving via docker push rather than via any of the three, flat design, clean vector art style, white background]

Prove the RBAC split to yourself

This is a two-minute experiment that will save you an afternoon later. Assign a second principal Reader on the registry and try to pull:

ACR_ID=$(az acr show -n $ACR -g $RG --query id -o tsv)

# Reader on the ARM resource — can see it, cannot pull it
az role assignment create --assignee <object-id> --role "Reader" --scope $ACR_ID

# What that principal actually needs in order to pull:
az role assignment create --assignee <object-id> --role "AcrPull" --scope $ACR_ID

Reader gets a 401 from <registry>.azurecr.io. AcrPull works and grants no ARM rights at all. That's the control-plane/data-plane line from Architecture, in two commands.

Teardown

# Terraform path
terraform destroy

# CLI path — one delete removes the registry, its images, and everything else in the group
az group delete -n $RG --yes --no-wait

rm -rf /tmp/acr-demo

Cleanup note. Deleting the resource group is the cleanest teardown Azure gives you, and it's a genuine advantage over AWS, where there is no equivalent "delete the container and everything inside it" primitive. Two ACR-specific caveats even so: if you enabled the soft delete policy, deleted artifacts are retained for the configured window ⚠️ verify current preview/GA status, and the registry name stays reserved while any soft-deleted state exists — so recreating with the same name may fail. And a CanNotDelete resource lock anywhere in the group will fail the delete with an error that reads like a permissions problem.

What to take to the next page

You now have a registry, an image, and a digest. What you do not have is anything you'd ship: the name is hard-coded, the tier is a guess, there's no network restriction, no retention policy, no pipeline identity, and no way to make the same thing exist in staging and prod without copy-paste. Deployment fixes each of those in turn.


Next: Deployment →

← Back to the Azure Container Registry overview · ← Previous: Architecture