4. Getting Started
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 registries → Create.
- On Basics: new resource group
rg-acr-demo, registry nameacrdemo<unique>, locationuksouth, 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 = falseis the default, but writing it makes the intent reviewable and makes a latertrueshow 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
CanNotDeleteresource 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