Skip to content
DevOps AI ToolKit
Core Guide · Cloud & Security

Azure CLI

An Azure CLI reference for engineers who operate real subscriptions — signing in, service principals and workload identity, RBAC scopes, deployments and what-if, quotas and capacity, throttling, and diagnosing the errors ARM actually returns.

Last reviewed September 2026 Reference · Cheat sheet · 30 min read

Technically validated: Commands target Azure CLI 2.x (`az`) against Azure Resource Manager. Where behaviour depends on a specific API surface — data-plane RBAC roles, Key Vault's two permission models, resource-provider registration — that is called out inline rather than assumed.

On this page

Almost every Azure problem an engineer actually hits falls into one of three buckets: it will not authenticate, it will not authorize, or it will not deploy. The portal hides which of the three you are in; the CLI tells you directly. This reference is organized around that — sign in, prove who you are, get the right role at the right scope, deploy, and read the error ARM gives back.

Signing in and choosing a subscription

The single most common cause of a confusing Azure error is being signed in to the wrong tenant or the wrong subscription. Check that before anything else.

Authentication & context

Command What it does Risk
az login
Interactive browser sign-in. Safe
az login --use-device-code
Sign in from a headless box, bastion or container. Safe
az login --tenant <tenantId>
Sign in against a SPECIFIC tenant — required for guest/multi-tenant accounts. Safe
az login --identity
Authenticate as the VM/container's managed identity. No secret involved. Safe
az login --service-principal -u <appId> -p <secret> --tenant <tenantId>
Non-interactive sign-in for automation. Caution
az account show -o table
Who am I, in which subscription and tenant. Run this FIRST when confused. Safe
az account list --all -o table
Every subscription the account can see, across tenants. Safe
az account set --subscription <name-or-id>
Change the active subscription for subsequent commands. Caution
az account get-access-token --query expiresOn
Confirm you hold a live token and when it dies. Safe
az logout
Clear the local token cache — the fix for a stale or wrong-tenant session. Safe
# The three-line orientation check. Almost every "it works locally" bug is here.
az account show --query "{user:user.name, sub:name, tenant:tenantId}" -o yaml
az account list --all --query "[].{name:name, id:id, tenant:tenantId}" -o table
az account set --subscription "prod-platform"

Service principals, managed identity and workload identity

Three ways for code to authenticate, in increasing order of how much you should prefer them.

Machine identity

Command What it does Risk
az ad sp create-for-rbac --name <n> --role Reader --scopes <scope>
Create a service principal AND a role assignment. Prints the secret ONCE. Caution
az ad sp list --display-name <n> -o table
Find an existing service principal. Safe
az ad app credential reset --id <appId>
Rotate a client secret. Invalidates the old one immediately. Destructive
az ad app federated-credential create --id <appId> --parameters creds.json
Passwordless OIDC trust — GitHub Actions with no stored secret. Caution
az identity create -g <rg> -n <name>
Create a user-assigned managed identity. Caution
az vm identity assign -g <rg> -n <vm>
Give a VM a system-assigned managed identity. Caution
az ad sp show --id <appId> --query appRoles
Inspect what an app registration actually exposes. Safe

For SDK-based apps, DefaultAzureCredential walks a chain of these — environment variables, then workload identity, then managed identity, then the developer’s az login session. That is convenient locally and a frequent source of confusion in production, because it can silently succeed as the wrong identity:

# What the SDK will pick up from the environment
env | grep -E '^AZURE_(CLIENT_ID|TENANT_ID|CLIENT_SECRET|FEDERATED)'

# Prove which identity is actually in play before blaming the code
az account show --query "{name:user.name, type:user.type}" -o yaml

When it fails, the message names every credential it tried — read the chain, not just the last line. Full walkthrough: DefaultAzureCredential failed.

RBAC: roles, scopes, and why Contributor is not enough

Authorization failures are the second bucket, and they hinge on two things people conflate: the scope a role is assigned at, and whether the operation is management plane or data plane.

Roles & assignments

Command What it does Risk
az role assignment list --assignee <id> --all -o table
Every assignment a principal holds, at every scope. Safe
az role assignment list --scope <scope> --include-inherited -o table
Who has access HERE, including inherited from above. Safe
az role assignment create --assignee <id> --role <role> --scope <scope>
Grant a role. Needs Owner or User Access Administrator. Caution
az role assignment delete --assignee <id> --role <role> --scope <scope>
Revoke. Takes effect within minutes, not instantly. Destructive
az role definition list --name <role> --query '[].permissions'
What a role actually permits — read this before assigning it. Safe
az provider operation show --namespace Microsoft.Storage
Every operation a provider defines, for building a custom role. Safe
az lock list --resource-group <rg> -o table
Locks block deletes/changes regardless of RBAC. Check when a delete 'silently' fails. Safe

Scope is hierarchical and inherits downward:

management group  →  subscription  →  resource group  →  individual resource

An assignment at a resource group covers everything in it; one at a resource covers only that resource. Grant at the narrowest scope that works.

That distinction maps directly onto the errors:

  • AuthorizationFailed — management plane. The principal has no role granting that operation at that scope.
  • AuthorizationPermissionMismatch — data plane. Usually Contributor where a Data role was required.
  • ServerFailedToAuthenticateRequest — the storage request never presented a valid credential; often a clock skew or a malformed SAS.
  • LinkedAuthorizationFailed — you have rights on the resource but not on something it references, like a subnet or a managed identity.
  • PrincipalNotFound — a freshly created service principal has not replicated yet. Retry with backoff; it is a timing problem, not a permissions one.

Resource groups, deployments and what-if

Resources & deployments

Command What it does Risk
az group create -n <rg> -l <region>
Create a resource group. Caution
az group delete -n <rg> --yes --no-wait
Deletes EVERYTHING inside it. There is no undo. Destructive
az resource list -g <rg> -o table
Everything in a group, with type and location. Safe
az deployment group what-if -g <rg> -f main.bicep
The plan step. Shows create/modify/delete BEFORE you apply. Safe
az deployment group create -g <rg> -f main.bicep -p @params.json
Apply a template to a resource group. Destructive
az deployment group create --mode Complete ...
Complete mode DELETES resources not in the template. Rarely what you want. Destructive
az deployment group list -g <rg> --query '[].{n:name,state:properties.provisioningState}' -o table
Deployment history and outcomes. Safe
az deployment group show -g <rg> -n <dep> --query properties.error
The REAL error, nested inside the deployment record. Safe
az provider register --namespace Microsoft.<Service>
Register a resource provider on the subscription. One-off, takes minutes. Caution
az provider list --query "[?registrationState=='NotRegistered'].namespace" -o tsv
Providers not yet registered — a common first-deploy blocker. Safe

The what-if output is the closest Azure gets to terraform plan, and it is free:

az deployment group what-if -g rg-prod -f main.bicep -p @prod.params.json
# Symbols: + create   ~ modify   - delete   = no change

Two provider-registration errors block a great many first deployments — MissingSubscriptionRegistration and NoRegisteredProviderFound. Both are fixed by az provider register, and both take a few minutes to propagate.

Quotas, SKUs and capacity

Three different failures look identical from a distance — “the VM would not create” — and each has a different fix.

# 1. QUOTA: am I allowed this many cores in this region?
az vm list-usage --location eastus \
  --query "[?contains(name.value,'cores')].{name:localName, used:currentValue, limit:limit}" -o table

# 2. SKU AVAILABILITY: is this size even offered here, and is it restricted?
az vm list-skus --location eastus --size Standard_D4s --all \
  --query "[].{name:name, zones:locationInfo[0].zones, restriction:restrictions[0].reasonCode}" -o table

# 3. CAPACITY: the size exists and I have quota, but the region/zone is full right now.

Which error means which

Command What it does Risk
QuotaExceeded / OperationNotAllowed
A subscription LIMIT. Request an increase — capacity is not the problem. Safe
SkuNotAvailable
That size is not offered in the region/zone, or is restricted for your subscription. Safe
AllocationFailed
Transient CAPACITY. The cluster is full; try another zone, size family or region. Safe

The distinction matters because the responses are completely different: a quota problem needs a support request, a SKU problem needs a different size or region, and an allocation failure often just needs a retry in another zone. Details in SkuNotAvailable, AllocationFailed, QuotaExceeded and vCPU quota.

Networking has its own quiet limits — PublicIpCountLimitReached and SubnetIsFull — which surface as deployment failures rather than as quota messages.

Throttling and querying at scale

Azure Resource Manager rate-limits per subscription. A loop of az calls across hundreds of resources will hit it.

# ARM tells you how much budget is left, in a response header
az group list --debug 2>&1 | grep -i 'x-ms-ratelimit-remaining-subscription-reads'

When you get a 429, read Retry-After and honour it — retrying immediately makes it worse. Then remove the loop entirely: Azure Resource Graph answers across every subscription in one server-side query, which is both faster and far cheaper against the rate limit.

# Instead of iterating subscriptions and resource groups:
az graph query -q "
  Resources
  | where type =~ 'microsoft.compute/virtualmachines'
  | project name, resourceGroup, location, properties.hardwareProfile.vmSize
  | order by name asc
" -o table

See 429 TooManyRequests for the backoff strategy.

Diagnosing a failed deployment

The CLI’s top-level error is frequently a summary. The useful detail is nested.

# 1. The actual error object from the deployment record
az deployment group show -g rg-prod -n main \
  --query "properties.error" -o json

# 2. Failed operations within that deployment, with per-resource status
az deployment operation group list -g rg-prod -n main \
  --query "[?properties.provisioningState=='Failed'].{res:properties.targetResource.resourceName, msg:properties.statusMessage}" -o json

# 3. The activity log — what the platform recorded, including who did it
az monitor activity-log list -g rg-prod --offset 1h \
  --query "[?level=='Error'].{op:operationName.localizedValue, status:status.value, caller:caller}" -o table

# 4. When all else fails, see the raw request and response
az deployment group create -g rg-prod -f main.bicep --debug

Common deployment blockers

Command What it does Risk
InvalidTemplateDeployment
Policy or a validation rule rejected it. The nested message names which. Safe
RequestDisallowedByPolicy
Azure Policy blocked it. Read the policy definition ID in the error. Safe
ResourceGroupBeingDeleted
A delete is still in flight. Wait — you cannot create into it. Safe
Conflict / another operation in progress
Concurrent writes to one resource. Serialise, then retry. Safe
InvalidApiVersionParameter
The template's apiVersion is not valid for that provider. Safe
ScopeLocked
A CanNotDelete or ReadOnly lock is in force. Locks beat RBAC. Safe

RequestDisallowedByPolicy deserves a note: it means the deployment was valid and Azure Policy refused it anyway. The error carries the policy assignment ID — look that up rather than editing the template blindly, because the policy usually exists for a reason.

AKS, ACR and Key Vault

The three services whose failures most often turn out to be identity problems wearing a different hat.

AKS, ACR & Key Vault

Command What it does Risk
az aks get-credentials -g <rg> -n <cluster>
Merge cluster credentials into kubeconfig. Caution
az aks get-credentials ... --overwrite-existing
Replace a stale entry — the fix for confusing kubectl auth errors. Caution
az aks nodepool list -g <rg> --cluster-name <c> -o table
Node pools, sizes, counts and provisioning state. Safe
az aks update -g <rg> -n <c> --attach-acr <acrName>
Grant the cluster AcrPull. The proper fix for ImagePullBackOff from ACR. Caution
az acr login -n <registry>
Authenticate Docker to ACR using your Azure identity. Safe
az acr repository list -n <registry> -o table
What is actually in the registry. Safe
az keyvault secret show --vault-name <v> -n <secret>
Read a secret — needs data-plane permission, not just Contributor. Caution
az keyvault show -n <v> --query properties.enableRbacAuthorization
Tells you WHICH permission model this vault uses. Safe

The AKS failures that look like Kubernetes problems but are not: ImagePullBackOff from ACR is usually a missing AcrPull assignment — --attach-acr fixes it properly — and ACR unauthorized is an expired az acr login. Genuine cluster-side issues are covered by NodeNotReady, insufficient resources and CrashLoopBackOff.

Output, queries and scripting

Making az scriptable

Command What it does Risk
-o table
Human-readable. Never parse it. Safe
-o tsv
The one to use in shell scripts — no quotes, no JSON. Safe
--query "[].{n:name, l:location}"
JMESPath projection. Applied client-side, after download. Safe
--only-show-errors
Suppress warnings so CI logs stay readable. Safe
--no-wait
Return immediately instead of polling a long operation. Caution
az <cmd> --debug
Full request/response, including the ARM error body. Safe
az config set core.output=table
Set a default output format persistently. Safe
az extension add --name <ext>
Install an extension (aks-preview, resource-graph, …). Caution
az upgrade
Update the CLI and its extensions. Caution
# A safe scripting shape: fail fast, no interactive prompts, machine-readable output.
set -euo pipefail
az config set core.only_show_errors=true --only-show-errors

vm_ids=$(az vm list -g rg-prod --query "[].id" -o tsv)
for id in $vm_ids; do
  az vm show --ids "$id" --query "{name:name, size:hardwareProfile.vmSize}" -o tsv
done

That loop is fine for a handful of VMs and wrong for a thousand — at that point it is one az graph query, for the throttling reasons above.

Troubleshooting specific errors

The Azure failures engineers hit most often, each with a dedicated guide:

For a code not listed here, the Entra sign-in error hub carries the full AADSTS lookup table, or paste the message into the Incident Assistant.

Production checklist

  • az account show before anything surprising. Wrong tenant and wrong subscription explain more failures than any bug.
  • Managed identity or workload identity over client secrets. A secret you never create cannot expire in production.
  • Assign roles at the narrowest scope that works, and prefer a group over a person.
  • Know whether the operation is management plane or data plane before granting Contributor again.
  • what-if before every deployment, and read the delete list — especially with --mode Complete.
  • Register resource providers as part of subscription bootstrap, not during the first deploy.
  • Honour Retry-After on 429, and replace inventory loops with az graph query.
  • Check enableRbacAuthorization before debugging a Key Vault permission.
  • Read properties.error from the deployment, not just the CLI’s summary line.
  • Rotate and inventory client secrets you cannot yet remove — expiry is a scheduled outage.

Frequently asked questions

Why does my service principal get AuthorizationFailed when it has Contributor?

Two usual causes. Either the assignment is at a narrower scope than the operation needs — Contributor on one resource group does not let you write to another — or the operation is data plane rather than management plane. Contributor on a storage account manages the account itself and grants no access to the blobs inside it; that needs a role like Storage Blob Data Contributor. Run az role assignment list --assignee <id> --all and compare the scope against what you are actually calling.

What is the difference between a service principal and a managed identity?

A service principal is an identity you create and hold a credential for — a client secret or certificate — which means you own the rotation, storage and leak risk. A managed identity is issued and rotated by Azure for a specific resource, with no secret you ever see or handle. If the workload runs inside Azure, use a managed identity. For CI running outside Azure, workload identity federation gives you the same secretless model by trusting the pipeline’s OIDC token.

How do I tell whether a VM failure is quota, SKU or capacity?

They are three different problems. QuotaExceeded and OperationNotAllowed mean a subscription limit — check az vm list-usage and request an increase. SkuNotAvailable means that size is not offered in that region or zone, or is restricted for your subscription — check az vm list-skus --all and look at the restriction reason. AllocationFailed means the size exists and you have quota, but the cluster is full right now — retry in another zone, a different size family, or another region.

Why does az work locally but fail in my pipeline?

Because they are different identities. Locally you are a user with your own role assignments; in CI you are a service principal or federated identity that usually has far fewer. Print the identity in the pipeline with az account show and list its assignments with az role assignment list --assignee <id> --all. The second most common cause is tenant: an account with guest access in several tenants can log in to the wrong one unless --tenant is explicit.

What does —mode Complete actually delete?

Every resource in the target resource group that your template does not declare. It is intended for resource groups fully owned by one template, and it is dangerous anywhere a person or another pipeline has also created something. Always run az deployment group what-if first and read the entries marked with -. Incremental mode, the default, never deletes.

How do I stop hitting 429 from ARM?

Stop iterating. Most throttling comes from scripts that loop over subscriptions or resource groups calling az resource list, which is one request each. az graph query answers the same question across every subscription in a single server-side query. Where you genuinely must loop, honour the Retry-After header, back off exponentially, and remember that --query filters after the download — it reduces what you see, not what you requested.

Did this solve your problem?

Continue learning

Related Core Guides that build on this one.

Written by James Joyner IV, Sr. Systems Software Engineer — for engineers who run what they build.

Last reviewed September 2026. Found an error or an out-of-date command? Tell us — accuracy is the point of a Core Guide.