chore: publish from main

This commit is contained in:
github-actions[bot]
2026-07-30 01:53:16 +00:00
parent cf3c70d8d2
commit 8df98cb45e
7 changed files with 732 additions and 1 deletions
@@ -0,0 +1,90 @@
---
name: azure-container-registry-cli
description: 'Manage Azure Container Registry via the az acr CLI including registries, images, cloud builds, ACR Tasks, authentication, tokens, geo-replication, and networking. Use when working with ACR, az acr commands, pushing/importing/purging container images in Azure, or when the user mentions Azure Container Registry.'
---
# Azure Container Registry CLI
Manage Azure Container Registry (ACR) resources using the `az acr` command group of the Azure CLI.
**CLI:** `az acr` ships with core Azure CLI — no extension required (the `acrtransfer` extension is only needed for export/import pipelines).
## Prerequisites
```bash
# Install Azure CLI
brew install azure-cli # macOS
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash # Linux
winget install Microsoft.AzureCLI # Windows
# Sign in and select subscription
az login
az account set --subscription {subscription-id}
```
## Quick Start
```bash
# Create a registry (SKU: Basic | Standard | Premium)
az acr create --resource-group {rg} --name {registry} --sku Standard
# Authenticate Docker/Podman against the registry
az acr login --name {registry}
# Build and push in the cloud — no local Docker needed
az acr build --registry {registry} --image app:v1 .
# Copy an image from another registry without pull/push
az acr import --name {registry} --source mcr.microsoft.com/hello-world:latest
# List repositories and tags
az acr repository list --name {registry} --output table
az acr repository show-tags --name {registry} --repository app --orderby time_desc
# Diagnose registry connectivity and configuration
az acr check-health --name {registry} --yes
```
## Key Principles
- **Prefer `az acr build` / ACR Tasks** over local `docker build` + `docker push`: builds run in Azure, work without a local daemon, and integrate with triggers.
- **Prefer `az acr import`** to move images between registries: it is server-side, faster, and requires no local storage.
- **Never enable the admin user for production** — use Microsoft Entra identities (RBAC roles `AcrPull`/`AcrPush`, or `Container Registry Repository Reader`/`Writer` on ABAC-enabled registries), repository-scoped tokens, or managed identities.
- **Premium-only features**: geo-replication, private endpoints, retention policies, connected registries, agent pools. (Repository-scoped tokens work in all tiers; zone redundancy is automatic in all tiers in supported regions.)
## CLI Structure
```
az acr
├── create / delete / list / show / update # Registry lifecycle
├── login # Docker credential helper (or --expose-token)
├── check-health / check-name / show-usage # Diagnostics & quota
├── build # Cloud image build (quick task)
├── run # Run a command / multi-step task once
├── task # ACR Tasks (triggers, timers, logs, runs)
├── agentpool # Dedicated task agent pools (Premium)
├── import # Server-side image copy into the registry
├── repository # List/show/delete/untag repos & tags, lock images
├── manifest # Manifest metadata, delete, OCI referrers
├── credential # Admin user credentials (avoid in production)
├── token / scope-map # Repository-scoped tokens (Premium)
├── replication # Geo-replication (Premium)
├── network-rule # IP network rules
├── private-endpoint-connection # Private Link approvals
├── config # content-trust, retention, soft-delete, ...
├── cache / credential-set # Artifact cache (pull-through cache) rules
├── webhook # Push/delete event webhooks
├── connected-registry # On-premises / IoT connected registries
└── export-pipeline / import-pipeline / pipeline-run # acrtransfer extension
```
## Reference Files
Read the relevant reference file based on the user's task. Each file contains complete command syntax and examples for its domain.
| File | When to read | Covers |
|---|---|---|
| `references/auth-and-security.md` | Login failures, permissions, CI/CD or AKS pull access | `az acr login` (incl. `--expose-token`), Entra RBAC roles, service principals, managed identities, `--attach-acr` for AKS, repository-scoped tokens & scope maps, admin user, content trust |
| `references/build-and-tasks.md` | Building images in Azure, automation, CI triggers | `az acr build`, `az acr run`, multi-step task YAML, `az acr task` (git/base-image/timer triggers, logs, runs), agent pools |
| `references/images-and-artifacts.md` | Managing repos, tags, cleanup, storage costs | `az acr import`, repository & manifest commands, untag vs delete, purge (`acr purge`), image locking, retention policy, soft delete, artifact cache, `show-usage` |
| `references/networking-and-geo.md` | Multi-region, private access, edge scenarios | Geo-replication, zone redundancy, private endpoints, network rules, dedicated data endpoints, connected registries, registry transfer pipelines |
@@ -0,0 +1,174 @@
# Authentication & Security
## Table of Contents
- [Individual Login](#individual-login)
- [Microsoft Entra RBAC Roles](#microsoft-entra-rbac-roles)
- [Service Principals](#service-principals)
- [Managed Identities](#managed-identities)
- [AKS Integration](#aks-integration)
- [Repository-Scoped Tokens](#repository-scoped-tokens)
- [Admin User](#admin-user)
- [Content Trust](#content-trust)
---
## Individual Login
```bash
# Standard login — wires Docker/Podman credentials via your az login identity
az acr login --name {registry}
# Without a Docker daemon: get an Entra access token and pipe it to docker login
LOGIN_SERVER=$(az acr show --name {registry} --query loginServer --output tsv)
az acr login --name {registry} --expose-token --query accessToken --output tsv | \
docker login $LOGIN_SERVER --username 00000000-0000-0000-0000-000000000000 --password-stdin
```
Notes:
- `az acr login` tokens are valid for 3 hours; re-run on expiry.
- Resolve the login server with `az acr show --name {registry} --query loginServer --output tsv` rather than hardcoding it: it is usually `{registry}.azurecr.io`, but sovereign clouds use other suffixes and registries with a domain name label scope get a hash suffix.
## Microsoft Entra RBAC Roles
The applicable data-plane roles depend on the registry's **role assignment permissions mode** — check it first:
```bash
az acr show --name {registry} --query roleAssignmentMode --output tsv
# LegacyRegistryPermissions -> use AcrPull/AcrPush/AcrDelete
# AbacRepositoryPermissions -> use Container Registry Repository Reader/Writer/Contributor
```
**Legacy mode (RBAC Registry Permissions):**
| Role | Permissions |
|---|---|
| `AcrPull` | Pull images |
| `AcrPush` | Pull + push images |
| `AcrDelete` | Delete images |
| `AcrImageSigner` | Sign images (content trust) |
| `Contributor`/`Owner` | Full control-plane management + push/pull |
**ABAC-enabled mode (RBAC Registry + ABAC Repository Permissions):** `AcrPull`/`AcrPush`/`AcrDelete` are **not honored**, and `Owner`/`Contributor`/`Reader` grant control-plane only. Use instead:
| Role | Permissions |
|---|---|
| `Container Registry Repository Reader` | Read images, tags, metadata (add ABAC conditions to scope to repositories) |
| `Container Registry Repository Writer` | Read + write/update |
| `Container Registry Repository Contributor` | Read + write + delete |
| `Container Registry Repository Catalog Lister` | List repositories — assign only when the identity must enumerate the catalog (e.g., `az acr repository list`); not needed for pull/push of known repositories |
```bash
# Get the registry resource ID
ACR_ID=$(az acr show --name {registry} --query id --output tsv)
# Grant pull access to a user, group, service principal, or managed identity
az role assignment create --assignee {principal-id} --scope $ACR_ID --role AcrPull
# List who has access
az role assignment list --scope $ACR_ID --output table
```
## Service Principals
For CI/CD systems that cannot use OIDC/managed identity:
```bash
# Create an SP scoped to pull only
ACR_ID=$(az acr show --name {registry} --query id --output tsv)
az ad sp create-for-rbac --name {sp-name} --scopes $ACR_ID --role AcrPull
# Docker login with the SP — pipe the secret via stdin, never pass it as an argument
# (printf with a quoted variable preserves whitespace/glob characters exactly)
printf '%s' "$SP_PASSWORD" | docker login $LOGIN_SERVER --username {appId} --password-stdin
```
Prefer federated credentials (OIDC) over SP passwords in GitHub Actions / Azure DevOps when possible.
## Managed Identities
For Azure compute (VM, App Service, Container Apps, Functions):
```bash
# Assign a system-assigned identity and grant it pull
az vm identity assign --name {vm} --resource-group {rg}
PRINCIPAL_ID=$(az vm show --name {vm} --resource-group {rg} --query identity.principalId --output tsv)
az role assignment create --assignee $PRINCIPAL_ID --scope $ACR_ID --role AcrPull
```
App Service / Container Apps then pull with `--assign-identity` + `--acr-identity` style flags of their own CLIs — no registry password needed.
## AKS Integration
```bash
# Attach at cluster creation
az aks create --name {cluster} --resource-group {rg} --attach-acr {registry}
# Attach/detach an existing cluster (grants AcrPull to the kubelet identity)
az aks update --name {cluster} --resource-group {rg} --attach-acr {registry}
az aks update --name {cluster} --resource-group {rg} --detach-acr {registry}
# Validate the cluster can reach the registry
az aks check-acr --name {cluster} --resource-group {rg} --acr {registry}.azurecr.io
```
`--attach-acr` requires Owner or User Access Administrator on the registry. Cross-subscription attach works by passing the full ACR resource ID.
⚠️ `--attach-acr` assigns `AcrPull`, which is **not honored on ABAC-enabled registries** (`roleAssignmentMode` = `AbacRepositoryPermissions`). For those, assign the ABAC roles to the kubelet identity manually:
```bash
ACR_ID=$(az acr show --name {registry} --query id --output tsv)
KUBELET_ID=$(az aks show --name {cluster} --resource-group {rg} \
--query identityProfile.kubeletidentity.objectId --output tsv)
az role assignment create --assignee $KUBELET_ID --scope $ACR_ID \
--role "Container Registry Repository Reader"
# "Container Registry Repository Catalog Lister" is NOT needed for pulls —
# only add it if the identity must list repositories
```
## Repository-Scoped Tokens
Available in all service tiers. Fine-grained, non-Entra credentials (e.g., external partners, IoT devices):
```bash
# 1. Create a scope map (actions: content/read, content/write, content/delete, metadata/read, metadata/write)
az acr scope-map create --name {scope-map} --registry {registry} \
--repository app content/read metadata/read \
--description "Pull-only access to app"
# 2. Create a token bound to the scope map
az acr token create --name {token} --registry {registry} --scope-map {scope-map}
# 3. Generate/rotate passwords (up to 2, optional expiry)
az acr token credential generate --name {token} --registry {registry} --password1 --expiration-in-days 30
# Login with the token — pipe the password via stdin, never pass it as an argument
printf '%s' "$TOKEN_PWD" | docker login $LOGIN_SERVER --username {token} --password-stdin
# Disable or delete
az acr token update --name {token} --registry {registry} --status disabled
az acr token delete --name {token} --registry {registry} --yes
```
## Admin User
Single account, full push/pull on the whole registry, not auditable per user — **keep disabled in production**:
```bash
az acr update --name {registry} --admin-enabled false # recommended
az acr credential show --name {registry} # view username/passwords (if enabled)
az acr credential renew --name {registry} --password-name password2 # rotate
```
Legitimate uses: quick local tests, services that only accept username/password and cannot use tokens.
## Content Trust (deprecated)
Docker Content Trust (DCT) is being retired: **since May 31, 2026 it cannot be enabled on new registries** (or on registries that never enabled it), and it will be removed entirely on March 31, 2028. Do not set up DCT — sign images with **Notation (Notary Project)** and store signatures as OCI artifacts instead; see "Transition from Docker Content Trust to Notary Project" in the ACR docs.
```bash
# Registries with legacy DCT only — inspect or disable the existing configuration
az acr config content-trust show --registry {registry}
az acr config content-trust update --registry {registry} --status disabled
```
Legacy DCT signers needed `AcrImageSigner` in addition to `AcrPush`.
@@ -0,0 +1,147 @@
# Builds & ACR Tasks
## Table of Contents
- [Quick Build (az acr build)](#quick-build-az-acr-build)
- [Run a Command or Multi-Step Task Once (az acr run)](#run-a-command-or-multi-step-task-once-az-acr-run)
- [ACR Tasks (az acr task)](#acr-tasks-az-acr-task)
- [Triggers](#triggers)
- [Multi-Step Task YAML](#multi-step-task-yaml)
- [Agent Pools](#agent-pools)
---
## Quick Build (az acr build)
Builds in Azure and pushes to the registry — no local Docker daemon required:
```bash
# Build from the current directory and push
az acr build --registry {registry} --image app:v1 .
# Custom Dockerfile, build args, target platform
az acr build --registry {registry} --image app:v1 \
--file docker/Dockerfile.prod \
--build-arg VERSION=1.2.3 \
--platform linux/amd64 .
# Cross-platform: each build produces ONE single-architecture image for the target platform
az acr build --registry {registry} --image app:v1-arm64 --platform linux/arm64 .
# For a true multi-arch image, build once per platform under arch-specific tags, then
# assemble and push a manifest list (docker manifest create/push, or docker buildx locally)
# Build directly from a Git repo (no local clone)
az acr build --registry {registry} --image app:v1 https://github.com/{org}/{repo}.git#{branch}:{folder}
# Build without pushing (validation only)
az acr build --registry {registry} --image app:test --no-push .
```
Notes:
- The build context is uploaded; use a `.dockerignore` to keep it small.
- Tag with a unique value per build (git SHA, run ID) — avoid relying on `latest`.
## Run a Command or Multi-Step Task Once (az acr run)
```bash
# Run a container command in the registry's task runner (context /dev/null = no upload)
az acr run --registry {registry} --cmd '{registry}.azurecr.io/app:v1' /dev/null
# Execute a multi-step task file against the current directory
az acr run --registry {registry} --file acb.yaml .
```
## ACR Tasks (az acr task)
Persistent, triggerable build definitions:
```bash
# Create a task that builds on every commit to main
az acr task create --registry {registry} --name build-app \
--image "app:{{.Run.ID}}" \
--context https://github.com/{org}/{repo}.git#main \
--file Dockerfile \
--git-access-token {pat} \
--commit-trigger-enabled true \
--base-image-trigger-enabled true
# Manually trigger, list, inspect
az acr task run --registry {registry} --name build-app
az acr task list --registry {registry} --output table
az acr task list-runs --registry {registry} --name build-app --output table
az acr task logs --registry {registry} --name build-app # latest run
az acr task logs --registry {registry} --run-id {run-id}
# Update / disable / delete
az acr task update --registry {registry} --name build-app --image "app:{{.Run.ID}}"
az acr task update --registry {registry} --name build-app --status Disabled
az acr task delete --registry {registry} --name build-app --yes
```
Useful run variables for `--image`: `{{.Run.ID}}`, `{{.Run.Commit}}`, `{{.Run.Branch}}`, `{{.Run.Date}}`.
⚠️ On **ABAC-enabled registries** (`roleAssignmentMode` = `AbacRepositoryPermissions`), tasks and quick builds/runs have no default access to the source registry. Pass `--source-acr-auth-id [caller]` to `az acr build`/`az acr run`, and `--source-acr-auth-id [system]` (or a user-assigned identity resource ID) to `az acr task create`/`update`, then grant that identity the `Container Registry Repository ...` roles. Ensure the task actually has that identity — add `--assign-identity [system]` at creation, or run `az acr task identity assign` on an existing task, before referencing it.
## Triggers
```bash
# Timer trigger (cron in UTC) — e.g., nightly rebuild
az acr task timer add --registry {registry} --name build-app \
--timer-name nightly --schedule "0 2 * * *"
az acr task timer list --registry {registry} --name build-app
az acr task timer remove --registry {registry} --name build-app --timer-name nightly
```
- **Commit trigger**: rebuild on push to the tracked branch (`--commit-trigger-enabled`).
- **Base image trigger**: rebuild automatically when the base image (e.g., a patched `mcr.microsoft.com` image) is updated (`--base-image-trigger-enabled`) — key for OS/framework patching.
- **Timer trigger**: cron schedules; also the standard way to schedule `acr purge` cleanup (see `images-and-artifacts.md`).
Tasks that access other registries or Azure resources can use an identity:
```bash
az acr task identity assign --registry {registry} --name build-app # system-assigned
az acr task credential add --registry {registry} --name build-app \
--login-server {other-registry}.azurecr.io --use-identity [system]
```
## Multi-Step Task YAML
`acb.yaml` — build, test, then push only on success:
```yaml
version: v1.1.0
steps:
- build: -t $Registry/app:{{.Run.ID}} -f Dockerfile .
- cmd: $Registry/app:{{.Run.ID}} run-tests
- push:
- $Registry/app:{{.Run.ID}}
```
```bash
# Run once
az acr run --registry {registry} --file acb.yaml .
# Or create a triggered task from the YAML
az acr task create --registry {registry} --name build-test-push \
--file acb.yaml \
--context https://github.com/{org}/{repo}.git#main \
--git-access-token {pat}
```
## Agent Pools
Premium SKU. Dedicated task compute — for more CPU, or one of the two supported ways to run tasks against a network-restricted registry (the other being trusted services + the task network bypass policy, see `networking-and-geo.md`):
```bash
az acr agentpool create --registry {registry} --name pool1 --tier S2 # S1/S2/S3/I6
# For the firewall/VNet scenario, the pool MUST be attached to a subnet that can
# reach the registry's private endpoint — without --subnet-id it runs outside the VNet
az acr agentpool create --registry {registry} --name pool1 --tier S2 \
--subnet-id /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnet}/subnets/{subnet}
az acr agentpool list --registry {registry} --output table
# Target the pool
az acr build --registry {registry} --agent-pool pool1 --image app:v1 .
az acr task create --registry {registry} --name build-app --agent-pool pool1 ...
```
@@ -0,0 +1,170 @@
# Images & Artifacts
## Table of Contents
- [Import Images (Server-Side Copy)](#import-images-server-side-copy)
- [Repositories & Tags](#repositories--tags)
- [Manifests](#manifests)
- [Untag vs Delete](#untag-vs-delete)
- [Purge Old Images (acr purge)](#purge-old-images-acr-purge)
- [Lock Images](#lock-images)
- [Retention Policy & Soft Delete](#retention-policy--soft-delete)
- [Artifact Cache (Pull-Through Cache)](#artifact-cache-pull-through-cache)
- [Storage Usage](#storage-usage)
---
## Import Images (Server-Side Copy)
Preferred over `docker pull` + `docker push` — no local storage, keeps multi-arch manifests intact:
```bash
# From a public registry
az acr import --name {registry} --source mcr.microsoft.com/hello-world:latest
az acr import --name {registry} --source docker.io/library/nginx:1.27 --image nginx:1.27
# From another ACR in the same tenant (by resource ID, no credentials needed)
az acr import --name {registry} \
--source app:v1 \
--registry /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.ContainerRegistry/registries/{src-registry}
# From a private registry with credentials
az acr import --name {registry} --source private.example.com/app:v1 \
--username {user} --password {password}
# Overwrite an existing tag
az acr import --name {registry} --source docker.io/library/nginx:1.27 --image nginx:1.27 --force
```
## Repositories & Tags
```bash
az acr repository list --name {registry} --output table
# Tags, newest first, with digest and timestamps
az acr repository show-tags --name {registry} --repository app \
--orderby time_desc --detail --output table
az acr repository show --name {registry} --image app:v1 # tag attributes
az acr repository show --name {registry} --repository app # repo attributes
```
## Manifests
```bash
# Metadata for all manifests in a repo (digest, tags, size, timestamps)
az acr manifest list-metadata --registry {registry} --name app --output table
# One manifest's metadata / raw content
az acr manifest show-metadata --registry {registry} --name app:v1
az acr manifest show --registry {registry} --name app@sha256:{digest}
# Find untagged (orphaned) manifests
az acr manifest list-metadata --registry {registry} --name app \
--query "[?tags==null].digest" --output tsv
```
## Untag vs Delete
```bash
# Untag: removes only the tag; manifest + layers remain (still pullable by digest)
az acr repository untag --name {registry} --image app:v1
# Delete by tag: deletes the whole manifest and ALL other tags pointing to it
az acr repository delete --name {registry} --image app:v1 --yes
# Delete by digest (precise)
az acr repository delete --name {registry} --image app@sha256:{digest} --yes
# Delete an entire repository
az acr repository delete --name {registry} --repository app --yes
```
⚠️ Deleting by tag deletes the underlying manifest — other tags on the same image disappear too. Untag first if you only want to retire a tag name.
## Purge Old Images (acr purge)
`acr purge` runs as an ACR Task (container `mcr.microsoft.com/acr/acr-cli`):
```bash
# Dry run first — ALWAYS
az acr run --registry {registry} \
--cmd "acr purge --filter 'app:.*' --ago 30d --untagged --dry-run" /dev/null
# Delete tags older than 30 days matching the regex, plus untagged manifests
az acr run --registry {registry} \
--cmd "acr purge --filter 'app:.*' --ago 30d --untagged" /dev/null
# Keep the 5 most recent tags regardless of age
az acr run --registry {registry} \
--cmd "acr purge --filter 'app:.*' --ago 0d --keep 5 --untagged" /dev/null
# Schedule as a nightly task
az acr task create --registry {registry} --name purge-old-images \
--cmd "acr purge --filter 'app:.*' --ago 30d --untagged" \
--context /dev/null --schedule "0 3 * * *"
```
`--filter` takes `repository:tag-regex` and can be repeated for multiple repos.
⚠️ `--untagged` ignores `--ago`: it deletes **every** untagged manifest, including ones created moments ago (mid-push images, referrer artifacts). Omit `--untagged` if recent untagged manifests must survive — the age cutoff only applies to tagged images matched by `--filter`.
## Lock Images
Prevent overwrite or deletion of critical tags (e.g., released versions):
```bash
# Read-only: cannot be overwritten or deleted
az acr repository update --name {registry} --image app:v1 --write-enabled false
# Undeletable but still overwritable
az acr repository update --name {registry} --image app:v1 --delete-enabled false
# Unlock
az acr repository update --name {registry} --image app:v1 --write-enabled true --delete-enabled true
```
## Retention Policy & Soft Delete
Two distinct policies that **cannot be enabled at the same time**. Retention policy requires **Premium**; soft delete (preview) is available in **all tiers** but doesn't support geo-replicated or artifact-cache-enabled registries.
```bash
# Retention policy (Premium): auto-delete untagged manifests after N days (0 = immediately)
az acr config retention update --registry {registry} \
--status enabled --days 7 --type UntaggedManifests
az acr config retention show --registry {registry}
# Soft delete (preview, all tiers): recover deleted artifacts within 1-90 days
az acr config soft-delete update --registry {registry} --status enabled --days 7
az acr repository list-deleted --name {registry}
az acr manifest restore --registry {registry} --name app:v1
```
## Artifact Cache (Pull-Through Cache)
Cache upstream images (Docker Hub, MCR, GHCR, quay.io, ECR Public) in your registry — avoids rate limits and centralizes provenance:
```bash
# Optional: credentials for authenticated upstream (secrets live in Key Vault)
az acr credential-set create --registry {registry} --name dockerhub-creds \
--login-server docker.io \
--username-id https://{vault}.vault.azure.net/secrets/dh-user \
--password-id https://{vault}.vault.azure.net/secrets/dh-pass
# Cache rule: docker.io/library/* -> {registry}.azurecr.io/dockerhub/*
az acr cache create --registry {registry} --name dockerhub-cache \
--source-repo "docker.io/library/*" --target-repo "dockerhub/*" \
--cred-set dockerhub-creds
az acr cache list --registry {registry} --output table
```
Then `docker pull {registry}.azurecr.io/dockerhub/nginx:1.27` fetches through the cache.
## Storage Usage
```bash
# Consumed storage vs SKU quota (Basic 10 GB / Standard 100 GB / Premium 500 GB included)
az acr show-usage --name {registry} --output table
```
Layers are deduplicated and shared across repositories; `show-usage` reports actual billable storage.
@@ -0,0 +1,147 @@
# Networking & Geo-Replication
## Table of Contents
- [Geo-Replication](#geo-replication)
- [Zone Redundancy](#zone-redundancy)
- [Private Endpoints (Private Link)](#private-endpoints-private-link)
- [Public Network Rules](#public-network-rules)
- [Dedicated Data Endpoints](#dedicated-data-endpoints)
- [Connected Registry](#connected-registry)
- [Registry Transfer Pipelines](#registry-transfer-pipelines)
Geo-replication, private endpoints, public IP network rules, dedicated data endpoints, connected registries, and transfer pipelines require the **Premium** SKU. Zone redundancy is automatic in every tier.
---
## Geo-Replication
One registry, one login server, images served from the nearest region:
```bash
az acr replication create --registry {registry} --location westeurope
az acr replication list --registry {registry} --output table
az acr replication show --registry {registry} --name westeurope
az acr replication delete --registry {registry} --name westeurope
# Regional endpoint status (useful for webhook/replication debugging)
az acr replication update --registry {registry} --name westeurope --region-endpoint-enabled true
```
Pushes replicate automatically; clients keep pulling `{registry}.azurecr.io` and Traffic Manager routes to the closest replica.
## Zone Redundancy
Zone redundancy is **enabled automatically for all registries, in all tiers (Basic/Standard/Premium), in regions that support availability zones** — no flag, SKU, or action required, and it cannot be disabled. Geo-replicas in supported regions are also zone-redundant by default.
Do not rely on the `zoneRedundancy` property or the legacy `--zone-redundancy` flag: the property is a deprecated artifact that may display `Disabled` even though the registry is fully zone-redundant. Registries in regions without availability-zone support are the only exception — migrate them (via `az acr import` or a transfer pipeline) to a supported region.
## Private Endpoints (Private Link)
```bash
# 1. Disable network policies on the endpoint subnet if needed, then create the endpoint
az network private-endpoint create --resource-group {rg} --name {registry}-pe \
--vnet-name {vnet} --subnet {subnet} \
--private-connection-resource-id $(az acr show --name {registry} --query id --output tsv) \
--group-ids registry \
--connection-name {registry}-pe-conn
# 2. Private DNS so {registry}.azurecr.io resolves to the private IP
az network private-dns zone create --resource-group {rg} --name privatelink.azurecr.io
az network private-dns link vnet create --resource-group {rg} \
--zone-name privatelink.azurecr.io --name {registry}-dns-link --virtual-network {vnet} --registration-enabled false
az network private-endpoint dns-zone-group create --resource-group {rg} \
--endpoint-name {registry}-pe --name default \
--private-dns-zone privatelink.azurecr.io --zone-name registry
# 3. Optionally shut off public access entirely
az acr update --name {registry} --public-network-enabled false
# Manage connection approvals
az acr private-endpoint-connection list --registry-name {registry} --output table
az acr private-endpoint-connection approve --registry-name {registry} --name {connection}
```
Notes:
- Each private endpoint creates records for the registry **and** its data endpoint(s) (`{registry}.{region}.data.azurecr.io`) — geo-replicated registries need one data record per region.
- With public access disabled, standard ACR Tasks agents cannot reach the registry — use a dedicated agent pool attached to a subnet in the VNet, or enable trusted services **and** the task network bypass policy (see below).
## Public Network Rules
Restrict public access to specific IPs instead of (or before) going fully private:
```bash
# Default-deny, then allow specific ranges
az acr update --name {registry} --default-action Deny
az acr network-rule add --name {registry} --ip-address 203.0.113.0/24
az acr network-rule list --name {registry}
az acr network-rule remove --name {registry} --ip-address 203.0.113.0/24
# Let trusted Azure services (e.g., Defender, ACI, image import) through the firewall
az acr update --name {registry} --allow-trusted-services true
```
⚠️ **Since June 1, 2025, `--allow-trusted-services` alone is NOT enough for ACR Tasks using a system-assigned managed identity** — without the task network bypass policy, their runs get 403 errors on a network-restricted registry. Enable it explicitly:
```bash
az resource update \
--namespace Microsoft.ContainerRegistry --resource-type registries \
--name {registry} --resource-group {rg} \
--api-version 2025-06-01-preview \
--set properties.networkRuleBypassAllowedForTasks=true
```
Alternatives that avoid the bypass entirely: run tasks in a VNet-attached agent pool, or run `acr purge` locally with the [acr-cli binary](https://github.com/azure/acr-cli). Tasks using a user-assigned identity are not affected.
## Dedicated Data Endpoints
Give layer downloads stable, registry-specific FQDNs (`{registry}.{region}.data.azurecr.io`) instead of shared storage endpoints — simplifies client-side firewall rules:
```bash
az acr update --name {registry} --data-endpoint-enabled true
az acr show-endpoints --name {registry}
```
## Connected Registry
On-premises / IoT edge mirror of a cloud registry:
```bash
# Parent registry must have a dedicated data endpoint
az acr update --name {registry} --data-endpoint-enabled true
az acr connected-registry create --registry {registry} --name {connected-name} \
--repository "app" "hello-world" \
--mode ReadOnly # or ReadWrite
az acr connected-registry list --registry {registry} --output table
az acr connected-registry get-settings --registry {registry} --name {connected-name} \
--parent-protocol https --generate-password 1
az acr connected-registry deactivate --registry {registry} --name {connected-name}
```
## Registry Transfer Pipelines
Move images between disconnected clouds/tenants via storage blobs (extension `acrtransfer`):
```bash
az extension add --name acrtransfer
# Export from source registry to a storage container (SAS token in Key Vault)
az acr export-pipeline create --resource-group {rg} --registry {src-registry} \
--name export-pipe \
--secret-uri https://{vault}.vault.azure.net/secrets/{sas-secret} \
--storage-container-uri https://{account}.blob.core.windows.net/{container}
# Import on the target side
az acr import-pipeline create --resource-group {rg} --registry {dst-registry} \
--name import-pipe \
--secret-uri https://{vault}.vault.azure.net/secrets/{sas-secret} \
--storage-container-uri https://{account}.blob.core.windows.net/{container}
# Run an export
az acr pipeline-run create --resource-group {rg} --registry {src-registry} \
--pipeline export-pipe --name run1 --pipeline-type export \
--artifacts app:v1 app:v2 --storage-blob transfer-blob-1
```
For simple same-cloud copies prefer `az acr import` (see `images-and-artifacts.md`).