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

7. Production

11 min read

The difference between a registry that works and a registry you can run. Five pillars, plus the one discipline — tag hygiene — that is more consequential for ACR than any setting.

[Image Prompt: 2D minimalistic diagram of five production pillars — security, cost, scaling, observability, reliability — arranged around a central Azure Container Registry resource, flat design, clean vector art style, white background]

Security

Turn off the admin user, and prove it stays off. It is a shared password with full push and pull that appears in logs as the registry's own name rather than as a person, and anyone with Contributor on the resource can read it with az acr credential show. Disabling it is one property; keeping it disabled is an Azure Policy assignment at the management group. Do both.

Least privilege on the data plane. The whole model:

Principal Role Why
AKS kubelet identity / Container App identity / App Service identity AcrPull Runtimes only ever read
Build pipeline federated identity AcrPush Push implies pull; that's fine
A cleanup job AcrDelete Separate from push, deliberately
Platform engineers Contributor on the registry, via PIM, time-bound Because Contributor is a data-plane escalation path via the admin credential
Developers AcrPull on non-prod registries only Prod images are not a development resource

Where the built-in roles are too broad: there is no built-in role that grants push without pull, and none that scope to a repository. If you need repository granularity, Premium scope maps and tokens are the only mechanism — and they issue a password, so treat each token as a secret with an owner, an expiry, and a rotation date. The honest alternative, and usually the better one, is more registries.

Keyless everywhere. Managed identity for every runtime, workload identity federation for every pipeline. If a connection string or a registry password appears anywhere in a config file, an app setting, or a Kubernetes secret, something in the design was skipped — see Integrations.

Network isolation. Premium + private endpoint + privatelink.azurecr.io, with public_network_access_enabled = false. Two things people get wrong:

  • The portal stops working for the Repositories blade, because it browses over the data plane from your browser. This is correct behaviour, not a misconfiguration. Plan for a jump host or accept CLI-from-inside-the-network as the browse path.
  • The layer-blob redirect goes to Microsoft-managed storage hostnames, so a firewall allowing only *.azurecr.io breaks pulls halfway. Enable dedicated data endpoints and allow-list <registry>.<region>.data.azurecr.io for every replica region.

Encryption. At rest with Microsoft-managed keys by default; customer-managed keys (Premium) route through a Key Vault key with a user-assigned identity and generally must be configured at creation ⚠️ verify current behaviour. In transit, TLS only — there is no plaintext option.

Supply chain. Three controls, in increasing order of maturity:

  1. Tag immutability — so an audited tag cannot be repointed. Cheap, high value, turn it on.
  2. Scan on push with Defender for Cloud, and act on the running findings before the repository findings.
  3. Sign and verify. Sign with Notation (Notary v2) using a key in Key Vault, and verify at Kubernetes admission with Ratify or an equivalent. Note that ACR's older content trust (Notary v1 / Docker Content Trust) is on a retirement path ⚠️ verify the current date — do not start new work on it.

Exfiltration. The export policy can block az acr import/export out of a locked-down Premium registry. It requires public network access to be disabled, and it will also block your own promotion pipeline — which is the point. Decide consciously.

Cost

What you actually pay for:

  • A fixed daily rate per tier, charged whether or not a single pull happens. This is the line people forget: an idle Premium registry has a non-trivial monthly cost.
  • Storage above the included allowance, per GiB per day (Basic ~10 GiB, Standard ~100 GiB, Premium ~500 GiB included ⚠️ verify current figures).
  • Each geo-replication region, billed at approximately a full additional Premium daily rate ⚠️ verify.
  • Egress — bandwidth leaving the region. Pulls from within the same region are the cheap case; a cluster in East US pulling from a West Europe registry pays both latency and money.
  • ACR Tasks compute, per CPU-second beyond a free monthly allowance ⚠️ verify.

The biggest cost trap, by a distance: untagged manifests. Every CI push to a moving tag (:latest, :main, :dev) orphans the previous manifest, which keeps its unshared layers alive forever. A pipeline running fifty times a day produces eighteen thousand orphaned manifests a year. The fix is a retention policy for untagged manifests ⚠️ verify tier availability:

az acr config retention update -r acrplatformprod --status enabled --days 14 --type UntaggedManifests

The second trap: replicas nobody removed. A replica added for a region you no longer deploy to bills every day and is invisible in the resource list unless you look at the registry's Replications blade.

The third: cross-region pulls. If a workload pulls from another region's registry on every pod start, you're paying egress on a hot path. Either geo-replicate or move the registry.

Concrete optimisations, in order of payoff:

  1. Enable untagged-manifest retention. Usually the single biggest win, and it's one command.
  2. Tag by immutable commit SHA rather than by a moving tag, so orphaning stops happening at all.
  3. Shrink images — multi-stage builds, distroless or Mariner bases. Smaller layers mean less storage, less egress, faster pulls, and less throttling. This is the optimisation that pays in four currencies.
  4. Audit replicas quarterly.
  5. Right-size the tier — but size by feature need, not by storage. If you don't need private endpoints, replication, CMK, or scope maps, Standard is fine and much cheaper.

What keeps billing when idle: the tier's daily rate, all stored bytes, and every replica. Only egress and Tasks are usage-driven. A registry is close to a fixed cost.

Scaling and limits

ACR has no instance count. It has tier-derived ceilings, and — pleasantly — almost all of them are scoped per registry, not per subscription:

Limit Scope Notes
Storage (included, then billed) Per registry Basic ~10 GiB / Standard ~100 GiB / Premium ~500 GiB ⚠️ verify; hard ceiling far higher
Read operations per minute Per registry Steps up with tier ⚠️ verify current figures
Write operations per minute Per registry Steps up with tier ⚠️ verify
Download / upload bandwidth Per registry Steps up with tier ⚠️ verify
Webhooks Per registry ~2 / ~10 / ~500 by tier ⚠️ verify
Geo-replications Per registry (Premium) Practically limited by regions and budget
Repositories and tags Per registry Very large; not a practical constraint ⚠️ verify
Registries per subscription Per subscription-per-region A quota you can raise via support ⚠️ verify
ACR Tasks concurrency Per registry, by tier Higher on Premium ⚠️ verify

Throttling is HTTP 429 with Retry-After. The realistic trigger is not steady-state traffic — it's a burst: an AKS node pool upgrade, a large scale-out, or a CI matrix building twenty variants at once, all pulling the same large image within seconds. Mitigations, most to least effective:

  1. Smaller images. A 200 MB image throttles at ten times the concurrency of a 2 GB one.
  2. Geo-replication, which spreads the load across regional replicas.
  3. Staggered rolloutsmaxSurge on node pool upgrades, deployment maxUnavailable.
  4. Raise the tier. Premium's headroom is real, but it's the answer that costs money to buy time.

Well-behaved clients (containerd, current docker, kubelet) honour Retry-After and back off. Hand-rolled scripts in a for loop do not, and are usually the actual cause.

Observability

Diagnostic settings are not on by default. Until you create one, you have platform metrics and nothing else — no record of who pulled what. Route these to a Log Analytics workspace:

Category What it answers
ContainerRegistryLoginEvents Who authenticated, from where, and whether it succeeded. Your audit trail
ContainerRegistryRepositoryEvents Push, pull, delete, per repository and tag. Your usage and forensics trail
AllMetrics Storage used, pull/push counts, agent-pool CPU for Tasks
az monitor diagnostic-settings create \
  -n diag-acr --resource $(az acr show -n acrplatformprod -g rg-platform --query id -o tsv) \
  --workspace <log-analytics-workspace-id> \
  --logs    '[{"category":"ContainerRegistryLoginEvents","enabled":true},
              {"category":"ContainerRegistryRepositoryEvents","enabled":true}]' \
  --metrics '[{"category":"AllMetrics","enabled":true}]'

Metrics worth alerting on:

  • StorageUsed trending up with no release cadence to explain it → your retention policy is off or ineffective.
  • Failed pull count / 401s in login events → a role assignment was removed, a token expired, or something is still using the admin user.
  • 429 responses → throttling; correlate with cluster scale events.

The KQL that answers the most common ACR questionwho pushed this image, and when?

ContainerRegistryRepositoryEvents
| where TimeGenerated > ago(30d)
| where Repository == "api" and OperationName == "Push"
| project TimeGenerated, Repository, Tag, Digest, Identity, CallerIpAddress, OperationName
| order by TimeGenerated desc

And the second most common — is anyone still pulling this repository, or can I delete it?

ContainerRegistryRepositoryEvents
| where TimeGenerated > ago(90d) and OperationName == "Pull"
| summarize Pulls = count(), LastPull = max(TimeGenerated) by Repository
| order by LastPull asc

The activity log covers control-plane changes — SKU changes, network rule edits, someone enabling the admin user. Alert on Microsoft.ContainerRegistry/registries/write in prod; the registry's shape should change rarely enough that every change is worth a notification.

Reliability

What the redundancy settings actually protect against:

Setting Protects against Doesn't protect against
Zone redundancy (Premium) Loss of one availability zone in a region Regional outage; accidental deletion
Geo-replication (Premium) Regional outage; also fixes cross-region pull latency and egress Accidental deletion — deletes replicate
Soft delete policy ⚠️ verify status Accidental artifact deletion, within the retention window Registry deletion; a determined purge
Resource lock (CanNotDelete) Accidental registry deletion Content deletion inside the registry

Note the gap that table exposes: nothing in the list is a backup. If you need a true independent copy, az acr import into a registry in a different subscription, on a schedule. For most teams the honest position is that images are rebuildable from source and the registry is a cache — but say that out loud and check that it's true, because "rebuildable" assumes the base image, the dependencies, and the build toolchain are all still available.

The failure drill worth running once: disable public network access on a staging registry with a private endpoint, then roll a deployment. You'll discover, in a controlled way, whether your DNS is right, whether your firewall allows the data endpoints, and whether anything still authenticates with the admin user. Every one of those is better found on a Tuesday.

Impact shape, restated because it drives your alerting: an ACR outage does not stop running pods. It stops new ones — deployments, scale-outs, node repairs, restarts, and anything with imagePullPolicy: Always. Impact is invisible for a while and then total. Alert on pull failures, not just on registry availability.

Availability SLA is quoted per tier ⚠️ verify current percentages, and it covers the registry endpoint. Geo-replication and zone redundancy improve your effective availability without changing the printed figure.

The discipline that matters more than any setting

Tag hygiene. Everything hard about running a registry traces back to tags being treated as if they were immutable when they aren't.

  • Turn on tag immutability. v1.2.0 should mean one thing forever.
  • Tag with the commit SHA in CI, and apply semantic tags only at release.
  • Deploy by digest. Every manifest, every Helm value, every az containerapp update should carry @sha256:…. Tags are for humans reading the portal.
  • Never deploy :latest. It defeats rollback, defeats geo-replication consistency, defeats scanning attribution, and defeats your ability to answer "what's running."
  • Retain untagged manifests for a short window, then delete them.

A registry with immutable tags, digest-based deployment, and a retention policy is a registry that mostly runs itself. One without them generates a steady trickle of incidents that all look different and all have the same cause.


Next: Interview Questions →

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