
Azure Admin
- 73 installs
- 70 repo stars
- Updated July 26, 2026
- rysweet/amplihack
Helps with ai & agent building tasks.
About
azure-admin is a Claude Code skill for ai & agent building. It helps solo builders move faster with AI-assisted development.
- azure-admin
- AI & Agent Building
- AI-coding skill
Azure Admin by the numbers
- 73 all-time installs (skills.sh)
- +1 installs in the week ending Jul 26, 2026 (Skillselion tracking)
- Ranked #5,499 of 16,556 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/rysweet/amplihack --skill azure-adminAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 73 |
|---|---|
| repo stars | ★ 70 |
| Last updated | July 26, 2026 |
| Repository | rysweet/amplihack ↗ |
What it does
Helps with ai & agent building tasks.
Files
Azure Administration Skill
Overview
This skill provides comprehensive Azure administration capabilities, covering identity management, resource orchestration, CLI tooling, and DevOps automation. It integrates Microsoft's Azure ecosystem including Azure CLI (az), Azure Developer CLI (azd), Entra ID (formerly Azure AD), and Azure MCP (Model Context Protocol) for AI-powered workflows.
Core Capabilities:
- Identity & Access Management: User provisioning, RBAC, service principals, managed identities
- Resource Management: Subscriptions, resource groups, ARM templates, Bicep deployments
- CLI & Tooling: az CLI patterns, azd workflows, PowerShell integration
- MCP Integration: Azure MCP server for AI-driven Azure operations
- DevOps Automation: CI/CD pipelines, infrastructure as code, deployment strategies
- Cost & Governance: Budget management, policy enforcement, compliance
Target Audience:
- Cloud administrators managing Azure environments
- DevOps engineers automating Azure deployments
- Security teams implementing RBAC and compliance
- Developers using Azure services and MCP integration
Philosophy Alignment: This skill follows amplihack principles: ruthless simplicity, working code only, clear module boundaries, and systematic workflows.
Quick Reference Matrix
Common Task Mapping
| Task | Primary Tool | Secondary Tools | Skill Doc Reference |
|---|---|---|---|
| Create user account | az cli | Entra ID Portal | @docs/user-management.md |
| Assign RBAC role | az cli | Azure Portal | @docs/role-assignments.md |
| Deploy resource group | az cli, Bicep | ARM templates | @docs/resource-management.md |
| Setup service principal | az cli | Portal | @docs/user-management.md#service-principals |
| Enable managed identity | az cli | Portal | @docs/user-management.md#managed-identities |
| Create resource | az cli, azd | Portal, Terraform | @docs/resource-management.md |
| Query resources | az cli --query | JMESPath | @docs/cli-patterns.md#querying |
| Bulk user operations | az cli + bash | PowerShell | @examples/bulk-user-onboarding.md |
| Environment provisioning | azd | az cli, Bicep | @examples/environment-setup.md |
| Audit role assignments | az cli | Azure Policy | @examples/role-audit.md |
| Cost analysis | az cli, Portal | Cost Management API | @docs/cost-optimization.md |
| MCP integration | Azure MCP | az cli | @docs/mcp-integration.md |
| CI/CD pipeline | Azure DevOps | GitHub Actions | @docs/devops-automation.md |
Command Pattern Reference
# Identity operations
az ad user create --display-name "Jane Doe" --user-principal-name jane@domain.com
az ad sp create-for-rbac --name myServicePrincipal --role Contributor
# Resource operations
az group create --name myResourceGroup --location eastus
az deployment group create --resource-group myRG --template-file main.bicep
# RBAC operations
az role assignment create --assignee user@domain.com --role Reader --scope /subscriptions/xxx
az role assignment list --assignee user@domain.com --all
# Query patterns
az vm list --query "[?powerState=='VM running'].{Name:name, RG:resourceGroup}"
az resource list --resource-type "Microsoft.Compute/virtualMachines" --query "[].{name:name, location:location}"
# Cost management
az consumption usage list --start-date 2025-01-01 --end-date 2025-01-31
az costmanagement query --type ActualCost --dataset-aggregation name=Cost,function=Sum
# Azure Developer CLI (azd)
azd init --template todo-nodejs-mongo
azd up # provision + deploy
azd env list
azd downTopic 1: Identity & Access Management
Manage Azure identities through Entra ID: users, groups, service principals, managed identities, and RBAC.
Common operations: User creation, group management, role assignment, service principal setup, managed identity configuration, RBAC auditing
See: @docs/user-management.md and @docs/role-assignments.md for complete guides
Quick example:
# Create user
az ad user create --display-name "Jane Doe" --user-principal-name jane@contoso.com --password "SecureP@ssw0rd!"
# Create group and add member
az ad group create --display-name "Engineering Team" --mail-nickname "engineering"
az ad group member add --group "Engineering Team" --member-id $(az ad user show --id jane@contoso.com --query id -o tsv)
# Create service principal
az ad sp create-for-rbac --name "myAppSP" --role Contributor --scopes /subscriptions/{sub-id}
# Enable managed identity
az vm identity assign --name myVM --resource-group myRG
# Assign RBAC role
az role assignment create --assignee jane@contoso.com --role Reader --scope /subscriptions/{sub-id}Key concepts:
- Users & Groups: Entra ID accounts, group-based permissions
- Service Principals: App authentication, certificate-based auth preferred
- Managed Identities: Azure-managed credentials, no secret rotation needed
- RBAC: Owner, Contributor, Reader, custom roles at multiple scopes
- Security: MFA enforcement, least privilege, regular access reviews
Best practices:
- Use groups for role assignments (not individual users)
- Prefer managed identities over service principals
- Rotate service principal credentials every 90 days
- Store credentials in Azure Key Vault
- Enable MFA for all administrative accounts
Topic 2: Resource Management
Organize and deploy Azure resources through subscriptions, resource groups, and infrastructure as code.
Common operations: Resource group creation, tagging strategy, ARM/Bicep deployment, resource locks, multi-region management
See: @docs/resource-management.md for advanced patterns
Quick example:
# Create resource group with tags
az group create --name myResourceGroup --location eastus
az group update --name myResourceGroup --tags Environment=Production CostCenter=IT
# Deploy Bicep template with validation
az deployment group validate --resource-group myRG --template-file main.bicep
az deployment group create --resource-group myRG --template-file main.bicep --parameters vmName=myVM
# Lock resource group to prevent deletion
az lock create --name DontDelete --resource-group myResourceGroup --lock-type CanNotDelete
# Query resources by tag
az resource list --tag Environment=Production --query "[].{Name:name, Type:type}"Resource hierarchy:
Management Groups (optional)
└── Subscriptions (billing boundary)
└── Resource Groups (logical container)
└── Resources (VMs, databases, storage, etc.)Bicep basics: Declarative IaC with cleaner syntax than ARM templates, transpiles to ARM JSON, modular and reusable.
Tagging strategy: Environment, CostCenter, Owner, Application, Criticality, BackupPolicy
Topic 3: CLI & Tooling
Master Azure CLI (az), Azure Developer CLI (azd), and query patterns for automation.
Common operations: Authentication, JMESPath queries, batch operations, azd workflows, PowerShell integration
See: @docs/cli-patterns.md for advanced scripting
Quick example:
# Azure CLI authentication
az login
az account set --subscription "My Subscription Name"
az account show
# JMESPath query patterns
az vm list --query "[?powerState=='VM running'].{Name:name, RG:resourceGroup}"
az resource list --query "[?contains(name, 'prod')]"
az vm list --query "sort_by([],&name)[0:5]" # Top 5 by name
# Azure Developer CLI (azd)
azd init --template todo-nodejs-mongo
azd up # provision + deploy in one command
azd env new development
azd monitor --logs
azd down # cleanupJMESPath essentials: Filter [?condition], Project [].{Name:name}, Sort sort_by([],&field), Contains contains(name, 'str')
azd structure: azure.yaml, infra/ (main.bicep), src/ (application code)
PowerShell: Install-Module -Name Az, Connect-AzAccount, Get-AzVM
Topic 4: MCP Integration
Use Azure MCP (Model Context Protocol) to enable AI-powered Azure operations through Claude Code and other AI workflows.
Common operations: List resources via MCP, query resource properties, execute az commands through MCP, AI-driven automation
See: @docs/mcp-integration.md for complete tool reference
Quick setup:
Install and configure:
npm install -g @modelcontextprotocol/server-azureAdd to ~/.config/claude-code/mcp.json:
{
"mcpServers": {
"azure": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-azure"],
"env": {
"AZURE_SUBSCRIPTION_ID": "your-subscription-id"
}
}
}
}Available MCP tools:
azure_list_resources: List resources by type/filterazure_get_resource: Get detailed resource infoazure_list_users: List Entra ID usersazure_list_role_assignments: List RBAC assignmentsazure_query: Execute Azure Resource Graph queriesazure_cli: Execute arbitrary az CLI commands
Usage example:
Ask Claude Code: "Show me all running VMs in my subscription"
Claude Code uses MCP tool:
{
"tool": "azure_list_resources",
"parameters": {
"resourceType": "Microsoft.Compute/virtualMachines",
"filter": "powerState eq 'VM running'"
}
}Topic 5: DevOps Automation
Automate Azure deployments through CI/CD pipelines, infrastructure as code, and GitOps workflows.
Common operations: Azure DevOps pipelines, GitHub Actions integration, Bicep deployments, blue-green deployments, testing
See: @docs/devops-automation.md for advanced patterns
Quick example - Azure DevOps YAML:
trigger:
- main
pool:
vmImage: "ubuntu-latest"
variables:
azureSubscription: "myServiceConnection"
stages:
- stage: Deploy
jobs:
- deployment: DeployInfra
environment: production
strategy:
runOnce:
deploy:
steps:
- task: AzureResourceManagerTemplateDeployment@3
inputs:
azureResourceManagerConnection: $(azureSubscription)
resourceGroupName: myRG
templateLocation: Linked artifact
csmFile: main.bicepQuick example - GitHub Actions:
name: Deploy to Azure
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy Bicep
uses: azure/arm-deploy@v1
with:
resourceGroupName: myRG
template: ./infra/main.bicepBest practices:
- Version control all IaC in Git
- Create reusable Bicep modules
- Separate parameter files per environment
- Validate templates before deployment (what-if)
- Document architecture decisions
Topic 6: Cost & Governance
Monitor and optimize Azure spending through cost management, budgets, and policy enforcement.
Common operations: Cost analysis, budget alerts, policy assignment, quota management, resource optimization
See: @docs/cost-optimization.md for detailed strategies
Quick example:
# View current month costs by resource group
az costmanagement query \
--type ActualCost \
--dataset-aggregation name=Cost,function=Sum \
--dataset-grouping name=ResourceGroup,type=Dimension \
--timeframe MonthToDate
# Get consumption usage details
az consumption usage list \
--start-date 2025-01-01 \
--end-date 2025-01-31 \
--query "[].{Date:usageStart, Service:meterName, Cost:pretaxCost}"
# Assign policy to enforce tagging
az policy assignment create \
--name "require-tag-environment" \
--policy "require-tag-on-resources" \
--params '{"tagName":{"value":"Environment"}}' \
--resource-group myRG
# Check VM quota usage
az vm list-usage --location eastus --output tableCost optimization strategies:
1. Right-size resources (use appropriate VM sizes) 2. Reserved instances (30-70% savings for 1-3 year commits) 3. Spot instances for fault-tolerant workloads 4. Auto-shutdown schedules for non-production 5. Storage tiering (move cold data to Archive) 6. Regular cleanup of unused resources
Azure Policy use cases:
- Require tags on resources
- Restrict resource locations
- Limit allowed VM SKUs
- Enforce encryption at rest
- Audit compliance
Troubleshooting
Common Issues
Authentication Errors:
az logout && az login --use-device-code
az account show # Verify tenant and subscriptionPermission Denied:
- Check RBAC:
az role assignment list --assignee {user-or-sp} - Verify resource provider:
az provider list --query "[?registrationState=='NotRegistered']" - Confirm proper scope (subscription vs resource group)
Resource Not Found:
- Verify subscription context:
az account show - Check resource group exists:
az group exists --name {rg-name} - Search across subscriptions:
az resource list --name {resource-name}
Quota Exceeded:
az vm list-usage --location eastus --output table
# Request quota increase through Azure Portal or support ticketCLI Tool Issues:
- Update to latest:
az upgrade - Clear cache:
rm -rf ~/.azure/ - Reinstall extensions:
az extension list-available
See: @docs/troubleshooting.md for comprehensive debugging guide
Certification Path
Azure Administrator Associate (AZ-104):
- Prerequisites: 6 months hands-on Azure experience
- Domains: Identity, governance, storage, compute, networking, monitoring
- Study Resources: @references/az-104-guide.md
- Practice: Azure free account, Microsoft Learn labs
Next Steps:
- Azure Solutions Architect Expert (AZ-305)
- Azure DevOps Engineer Expert (AZ-400)
- Azure Security Engineer Associate (AZ-500)
Further Learning
Documentation:
- @docs/user-management.md - Complete user and identity operations
- @docs/role-assignments.md - RBAC patterns and custom roles
- @docs/resource-management.md - Advanced resource operations
- @docs/mcp-integration.md - MCP tools and workflows
- @docs/cli-patterns.md - Advanced CLI scripting
- @docs/devops-automation.md - CI/CD and GitOps
- @docs/cost-optimization.md - Cost management strategies
- @docs/troubleshooting.md - Debugging and resolution
Examples:
- @examples/bulk-user-onboarding.md - Automated user provisioning
- @examples/environment-setup.md - Complete environment deployment
- @examples/role-audit.md - RBAC compliance auditing
- @examples/mcp-workflow.md - AI-powered Azure operations
References:
- @references/microsoft-learn.md - Official learning paths
- @references/az-104-guide.md - Certification preparation
- @references/api-references.md - API and SDK documentation
Azure CLI Patterns and Best Practices
Advanced patterns for Azure CLI scripting, querying, and automation.
Table of Contents
1. JMESPath Query Patterns 2. Batch Operations 3. Error Handling 4. Output Formatting 5. Scripting Best Practices 6. Performance Optimization
JMESPath Query Patterns
JMESPath is the query language used by Azure CLI for filtering and transforming output.
Basic Filtering
# Select specific fields
az vm list --query "[].{Name:name, Location:location, RG:resourceGroup}"
# Filter by property value
az vm list --query "[?location=='eastus']"
# Multiple conditions (AND)
az vm list --query "[?location=='eastus' && powerState=='VM running']"
# Multiple conditions (OR) - use logical operators
az vm list --query "[?location=='eastus' || location=='westus']"
# Negate condition
az vm list --query "[?powerState!='VM deallocated']"String Operations
# Contains substring
az resource list --query "[?contains(name, 'prod')]"
# Starts with
az resource list --query "[?starts_with(name, 'vm-')]"
# Ends with
az resource list --query "[?ends_with(name, '-prod')]"
# Case-insensitive matching (convert to lowercase)
az resource list --query "[?contains(to_lower(name), 'production')]"Array Operations
# Get first element
az vm list --query "[0]"
# Get last element
az vm list --query "[-1]"
# Get elements by index range
az vm list --query "[0:5]" # First 5 elements
# Length/count
az vm list --query "length([])"
# Filter and count
az vm list --query "length([?location=='eastus'])"
# Map/projection with array
az vm list --query "[].{Name:name, Tags:tags.Environment}"Nested Property Access
# Access nested properties
az vm list --query "[].{Name:name, OS:storageProfile.osDisk.osType}"
# Access array elements in nested objects
az vm list --query "[].{Name:name, NICs:networkProfile.networkInterfaces[].id}"
# Flatten nested arrays
az vm list --query "[].networkProfile.networkInterfaces[].id | []"Sorting and Limiting
# Sort ascending
az vm list --query "sort_by([], &name)"
# Sort descending
az vm list --query "reverse(sort_by([], &name))"
# Sort by multiple keys
az vm list --query "sort_by([], &[location, name])"
# Sort by numeric property
az vm list --query "sort_by([], &to_number(properties.hardwareProfile.vmSize))"
# Limit results (first 10)
az vm list --query "[0:10]"Aggregations
# Count by property
az vm list --query "group_by([], &location) | keys(@)"
# Sum (requires jmespath-terminal extension or scripting)
az consumption usage list --query "sum([].quantity)"
# Max value
az vm list --query "max_by([], &properties.hardwareProfile.vmSize)"
# Min value
az vm list --query "min_by([], &name)"Complex Queries
# Combine multiple operations
az vm list --query "[?location=='eastus'] | [?powerState=='VM running'] | sort_by([], &name) | [].{Name:name, RG:resourceGroup}"
# Conditional output
az vm list --query "[].{Name:name, Status:powerState || 'Unknown'}"
# Type conversion
az vm list --query "[].{Name:name, SizeCode:to_number(properties.hardwareProfile.vmSize[-1:])}"
# Merge properties from different levels
az vm list --query "[].{Name:name, Location:location, Tags:tags, VMSize:properties.hardwareProfile.vmSize}"Batch Operations
Parallel Processing
# Process items in parallel with xargs
az vm list --query "[].id" -o tsv | \
xargs -I {} -P 5 az vm start --ids {}
# -P 5 means 5 parallel processes
# Adjust based on API rate limits and system resourcesBulk Resource Operations
#!/bin/bash
# bulk-tag-resources.sh
RESOURCE_GROUP="myResourceGroup"
TAG_KEY="Environment"
TAG_VALUE="Production"
# Get all resource IDs
RESOURCE_IDS=$(az resource list \
--resource-group "$RESOURCE_GROUP" \
--query "[].id" -o tsv)
# Tag each resource
echo "$RESOURCE_IDS" | while read -r resource_id; do
echo "Tagging: $resource_id"
az resource tag \
--tags "$TAG_KEY=$TAG_VALUE" \
--ids "$resource_id"
doneBulk User Creation
#!/bin/bash
# bulk-create-users.sh
CSV_FILE="users.csv"
LOG_FILE="user-creation-$(date +%Y%m%d-%H%M%S).log"
# Process CSV (skip header)
tail -n +2 "$CSV_FILE" | while IFS=, read -r display_name upn password department; do
echo "Creating: $display_name ($upn)" | tee -a "$LOG_FILE"
az ad user create \
--display-name "$display_name" \
--user-principal-name "$upn" \
--password "$password" \
--department "$department" \
--force-change-password-next-sign-in true \
2>&1 | tee -a "$LOG_FILE"
if [ ${PIPESTATUS[0]} -eq 0 ]; then
echo "✓ Success: $upn" | tee -a "$LOG_FILE"
else
echo "✗ Failed: $upn" | tee -a "$LOG_FILE"
fi
# Rate limiting
sleep 1
done
echo "User creation complete. Log: $LOG_FILE"Bulk Role Assignment
#!/bin/bash
# bulk-assign-roles.sh
GROUP_NAME="Engineering Team"
ROLE="Contributor"
RESOURCE_GROUPS=("app1-rg" "app2-rg" "app3-rg")
# Get group object ID
GROUP_ID=$(az ad group show --group "$GROUP_NAME" --query id -o tsv)
if [ -z "$GROUP_ID" ]; then
echo "Error: Group '$GROUP_NAME' not found"
exit 1
fi
# Assign role to each resource group
for rg in "${RESOURCE_GROUPS[@]}"; do
echo "Assigning $ROLE to $GROUP_NAME in $rg..."
az role assignment create \
--assignee "$GROUP_ID" \
--role "$ROLE" \
--resource-group "$rg"
if [ $? -eq 0 ]; then
echo "✓ Assigned to $rg"
else
echo "✗ Failed for $rg"
fi
doneError Handling
Basic Error Checking
#!/bin/bash
# Check command success
if az vm start --name myVM --resource-group myRG; then
echo "VM started successfully"
else
echo "Failed to start VM"
exit 1
fi
# Capture exit code
az vm show --name myVM --resource-group myRG
EXIT_CODE=$?
if [ $EXIT_CODE -eq 0 ]; then
echo "VM exists"
elif [ $EXIT_CODE -eq 3 ]; then
echo "VM not found"
else
echo "Unexpected error: $EXIT_CODE"
exit 1
fiRetry Logic
#!/bin/bash
# retry-command.sh
retry_command() {
local max_attempts=3
local delay=5
local attempt=1
local cmd="$@"
while [ $attempt -le $max_attempts ]; do
echo "Attempt $attempt/$max_attempts: $cmd"
if $cmd; then
echo "✓ Command succeeded"
return 0
else
echo "✗ Command failed"
if [ $attempt -lt $max_attempts ]; then
echo "Retrying in ${delay}s..."
sleep $delay
delay=$((delay * 2)) # Exponential backoff
fi
fi
attempt=$((attempt + 1))
done
echo "Command failed after $max_attempts attempts"
return 1
}
# Usage
retry_command az vm start --name myVM --resource-group myRGValidation Before Execution
#!/bin/bash
# validate-before-deploy.sh
RESOURCE_GROUP="myResourceGroup"
TEMPLATE_FILE="template.bicep"
# Check if resource group exists
if ! az group exists --name "$RESOURCE_GROUP" | grep -q "true"; then
echo "Error: Resource group '$RESOURCE_GROUP' does not exist"
exit 1
fi
# Validate template
echo "Validating template..."
if ! az deployment group validate \
--resource-group "$RESOURCE_GROUP" \
--template-file "$TEMPLATE_FILE"; then
echo "Template validation failed"
exit 1
fi
# Check what-if
echo "Checking deployment changes..."
az deployment group what-if \
--resource-group "$RESOURCE_GROUP" \
--template-file "$TEMPLATE_FILE"
# Confirm with user
read -p "Proceed with deployment? (yes/no): " confirm
if [ "$confirm" != "yes" ]; then
echo "Deployment cancelled"
exit 0
fi
# Deploy
echo "Deploying..."
az deployment group create \
--resource-group "$RESOURCE_GROUP" \
--template-file "$TEMPLATE_FILE"Output Formatting
Table Output
# Default table format
az vm list --output table
# Custom table columns
az vm list \
--query "[].{Name:name, Location:location, Status:powerState}" \
--output table
# Sorted table
az vm list \
--query "sort_by([], &name) | [].{Name:name, Location:location}" \
--output tableJSON Output
# Pretty JSON
az vm show --name myVM --resource-group myRG --output json
# Compact JSON
az vm show --name myVM --resource-group myRG --output json | jq -c
# Save to file
az vm list --output json > vms.json
# Process with jq
az vm list --output json | jq '.[] | select(.location=="eastus")'TSV Output for Scripting
# Tab-separated values (easy to parse)
az vm list --query "[].{Name:name, RG:resourceGroup}" --output tsv
# Process with while loop
az vm list --query "[].name" -o tsv | while read vm_name; do
echo "Processing: $vm_name"
# Do something with $vm_name
done
# Direct to xargs
az vm list --query "[].id" -o tsv | xargs -I {} az vm start --ids {}YAML Output
# Human-readable YAML
az vm show --name myVM --resource-group myRG --output yaml
# Multiple resources
az vm list --output yaml > vms.yamlScripting Best Practices
Script Template
#!/bin/bash
set -euo pipefail # Exit on error, undefined variables, pipe failures
IFS=$'\n\t' # Better word splitting
# Script configuration
readonly SCRIPT_NAME=$(basename "$0")
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly LOG_FILE="${SCRIPT_DIR}/${SCRIPT_NAME%.sh}-$(date +%Y%m%d-%H%M%S).log"
# Logging function
log() {
echo "[$(date +'%Y-%m-%d %H:%M:%S')] $*" | tee -a "$LOG_FILE"
}
# Error handler
error_exit() {
log "ERROR: $1"
exit 1
}
# Cleanup function
cleanup() {
log "Cleaning up..."
# Add cleanup logic here
}
trap cleanup EXIT
# Main logic
main() {
log "Starting $SCRIPT_NAME"
# Your Azure operations here
az vm list --output table >> "$LOG_FILE" 2>&1
log "Script completed successfully"
}
main "$@"Parameter Validation
#!/bin/bash
# Required parameters
RESOURCE_GROUP="${1:-}"
VM_NAME="${2:-}"
if [ -z "$RESOURCE_GROUP" ] || [ -z "$VM_NAME" ]; then
echo "Usage: $0 <resource-group> <vm-name>"
exit 1
fi
# Validate resource group exists
if ! az group exists --name "$RESOURCE_GROUP" | grep -q "true"; then
echo "Error: Resource group '$RESOURCE_GROUP' not found"
exit 1
fi
# Validate VM exists
if ! az vm show --name "$VM_NAME" --resource-group "$RESOURCE_GROUP" >/dev/null 2>&1; then
echo "Error: VM '$VM_NAME' not found in resource group '$RESOURCE_GROUP'"
exit 1
fi
echo "✓ Parameters validated"Idempotent Operations
#!/bin/bash
# create-resource-group.sh - Idempotent
RESOURCE_GROUP="myResourceGroup"
LOCATION="eastus"
# Check if resource group exists
if az group exists --name "$RESOURCE_GROUP" | grep -q "true"; then
echo "Resource group '$RESOURCE_GROUP' already exists"
else
echo "Creating resource group '$RESOURCE_GROUP'..."
az group create --name "$RESOURCE_GROUP" --location "$LOCATION"
echo "✓ Resource group created"
fiPerformance Optimization
Reduce API Calls
# Inefficient: Multiple API calls
for vm in $(az vm list --query "[].name" -o tsv); do
az vm show --name "$vm" --resource-group myRG
done
# Efficient: Single API call with query
az vm list --resource-group myRG --query "[].{Name:name, Location:location, Status:powerState}"Use --no-wait for Long Operations
# Start multiple VMs without waiting
for vm in vm1 vm2 vm3; do
az vm start --name "$vm" --resource-group myRG --no-wait
done
# Check status later
az vm list --resource-group myRG --query "[].{Name:name, Status:powerState}"Cache Reusable Data
#!/bin/bash
# Cache subscription ID (avoid repeated API calls)
SUBSCRIPTION_ID=$(az account show --query id -o tsv)
# Cache resource group list
RESOURCE_GROUPS=$(az group list --query "[].name" -o tsv)
# Use cached data
echo "Operating on subscription: $SUBSCRIPTION_ID"
for rg in $RESOURCE_GROUPS; do
echo "Processing: $rg"
# Operations using $rg
doneParallel Execution with Background Jobs
#!/bin/bash
# Start multiple operations in background
for vm in vm1 vm2 vm3; do
(
az vm start --name "$vm" --resource-group myRG
echo "✓ Started: $vm"
) &
done
# Wait for all background jobs to complete
wait
echo "All VMs started"Related Documentation
- @user-management.md - CLI patterns for identity operations
- @resource-management.md - CLI patterns for resource operations
- @role-assignments.md - CLI patterns for RBAC operations
- @mcp-integration.md - When to use MCP vs CLI
- @troubleshooting.md - Debugging CLI issues
Cost Optimization in Azure
Comprehensive guide to managing and optimizing Azure costs through monitoring, rightsizing, and governance.
Table of Contents
1. Cost Management Basics 2. Cost Analysis and Reporting 3. Optimization Strategies 4. Budgets and Alerts 5. Azure Policy for Cost Governance 6. Automation
Cost Management Basics
Understanding Azure Costs
Azure costs consist of:
- Compute: VMs, App Services, Functions, Containers
- Storage: Blob, Files, Disks, managed disks
- Networking: Data transfer, VPN Gateway, Load Balancer
- Databases: SQL Database, Cosmos DB, managed instances
- Additional Services: Monitoring, backup, DevOps
Cost Factors
1. Resource Type: Different SKUs have different pricing 2. Region: Costs vary by Azure region 3. Usage: Pay-per-use vs. reserved capacity 4. Data Transfer: Egress charges apply 5. Licensing: Bring your own license (BYOL) savings
Viewing Current Costs
# Show current month costs
az consumption usage list \
--start-date $(date -u -d '1 month ago' +%Y-%m-%d) \
--end-date $(date -u +%Y-%m-%d) \
--output table
# Cost by resource group
az costmanagement query \
--type ActualCost \
--dataset-aggregation name=Cost,function=Sum \
--dataset-grouping name=ResourceGroup,type=Dimension \
--timeframe MonthToDate
# Cost by resource type
az costmanagement query \
--type ActualCost \
--dataset-aggregation name=Cost,function=Sum \
--dataset-grouping name=ResourceType,type=Dimension \
--timeframe MonthToDateCost Analysis and Reporting
Generate Cost Report
#!/bin/bash
# cost-report.sh
OUTPUT_FILE="cost-report-$(date +%Y%m%d).csv"
START_DATE=$(date -u -d '30 days ago' +%Y-%m-%d)
END_DATE=$(date -u +%Y-%m-%d)
echo "Generating cost report for $START_DATE to $END_DATE..."
# Get costs by resource group
az costmanagement query \
--type ActualCost \
--dataset-aggregation name=Cost,function=Sum \
--dataset-grouping name=ResourceGroup,type=Dimension \
--timeframe Custom \
--timePeriod from="$START_DATE" to="$END_DATE" \
--query "properties.rows" -o json | \
jq -r '.[] | [.[0], .[1], .[2]] | @csv' > "$OUTPUT_FILE"
echo "Report saved to: $OUTPUT_FILE"
# Display top 10 most expensive resource groups
echo ""
echo "Top 10 Most Expensive Resource Groups:"
sort -t',' -k2 -rn "$OUTPUT_FILE" | head -10 | column -t -s','Cost by Tag
# Analyze costs by tag (e.g., Environment tag)
az costmanagement query \
--type ActualCost \
--dataset-aggregation name=Cost,function=Sum \
--dataset-grouping name=Tag,type=Dimension \
--dataset-filter '{
"and": [
{
"dimensions": {
"name": "TagKey",
"operator": "In",
"values": ["Environment"]
}
}
]
}' \
--timeframe MonthToDateForecast Future Costs
# Forecast costs for next 30 days
az costmanagement forecast \
--type ActualCost \
--dataset-aggregation name=Cost,function=Sum \
--timeframe Custom \
--timePeriod from=$(date -u +%Y-%m-%d) to=$(date -u -d '+30 days' +%Y-%m-%d)Optimization Strategies
1. Right-Size Virtual Machines
Identify Oversized VMs:
# List VMs with CPU < 10% average utilization
az monitor metrics list \
--resource {vm-resource-id} \
--metric "Percentage CPU" \
--start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--interval PT1H \
--aggregation Average \
--query "value[].timeseries[].data[].average | avg(@)"Resize VM:
# List available sizes
az vm list-sizes --location eastus --output table
# Resize VM (deallocates first)
az vm resize \
--resource-group myRG \
--name myVM \
--size Standard_B2s2. Reserved Instances
Reserved instances provide up to 72% savings for 1-year or 3-year commitments.
# View available reserved instance SKUs
az reservations catalog show \
--subscription-id {subscription-id} \
--reserved-resource-type VirtualMachines \
--location eastus
# Calculate savings from reserved instances
# (Use Azure Portal for reservation purchases)3. Spot VMs
Spot VMs offer up to 90% savings for fault-tolerant workloads.
# Create spot VM
az vm create \
--resource-group myRG \
--name mySpotVM \
--image Ubuntu2204 \
--priority Spot \
--max-price 0.05 \
--eviction-policy Deallocate4. Auto-Shutdown for Dev/Test VMs
# Enable auto-shutdown at 7 PM UTC
az vm auto-shutdown \
--resource-group myRG \
--name myVM \
--time 1900 \
--location eastus
# Disable auto-shutdown
az vm auto-shutdown --resource-group myRG --name myVM --location eastus --time ""5. Delete Unused Resources
Find Orphaned Disks:
# List unattached managed disks
az disk list --query "[?diskState=='Unattached'].{Name:name, RG:resourceGroup, Size:diskSizeGb, SKU:sku.name}" --output table
# Delete unattached disks
az disk list --query "[?diskState=='Unattached'].id" -o tsv | \
xargs -I {} az disk delete --ids {} --yesFind Orphaned NICs:
# List unattached network interfaces
az network nic list --query "[?virtualMachine==null].{Name:name, RG:resourceGroup}" --output table
# Delete unattached NICs
az network nic list --query "[?virtualMachine==null].id" -o tsv | \
xargs -I {} az network nic delete --ids {} --yesFind Orphaned Public IPs:
# List unassociated public IPs
az network public-ip list --query "[?ipConfiguration==null].{Name:name, RG:resourceGroup}" --output table
# Delete unassociated public IPs
az network public-ip list --query "[?ipConfiguration==null].id" -o tsv | \
xargs -I {} az network public-ip delete --ids {} --yes6. Storage Cost Optimization
Move to Cool/Archive Tiers:
# Set blob access tier to Cool
az storage blob set-tier \
--account-name mystorageaccount \
--container-name mycontainer \
--name myblob \
--tier Cool
# Lifecycle management policy (JSON)
az storage account management-policy create \
--account-name mystorageaccount \
--policy @policy.json
# policy.json example:
{
"rules": [
{
"enabled": true,
"name": "move-to-cool",
"type": "Lifecycle",
"definition": {
"actions": {
"baseBlob": {
"tierToCool": {
"daysAfterModificationGreaterThan": 30
},
"tierToArchive": {
"daysAfterModificationGreaterThan": 90
},
"delete": {
"daysAfterModificationGreaterThan": 365
}
}
},
"filters": {
"blobTypes": ["blockBlob"]
}
}
}
]
}7. Scale Down Non-Production Resources
#!/bin/bash
# scale-down-dev.sh - Scale down dev environments
# App Service Plans
az appservice plan list --query "[?tags.Environment=='Development'].{Name:name, RG:resourceGroup}" -o tsv | \
while read -r name rg; do
az appservice plan update --name "$name" --resource-group "$rg" --sku B1
echo "✓ Scaled down App Service Plan: $name"
done
# SQL Databases
az sql db list --query "[?tags.Environment=='Development'].{Name:name, Server:serverName, RG:resourceGroup}" -o tsv | \
while read -r name server rg; do
az sql db update --name "$name" --server "$server" --resource-group "$rg" --service-objective S0
echo "✓ Scaled down SQL Database: $name"
doneBudgets and Alerts
Create Budget
# Create monthly budget
az consumption budget create \
--budget-name "MonthlyBudget" \
--amount 1000 \
--category Cost \
--time-grain Monthly \
--start-date $(date -u +%Y-%m-01T00:00:00Z) \
--end-date $(date -u -d '+1 year' +%Y-%m-01T00:00:00Z) \
--resource-group myRG
# Create budget with email notification at 80% and 100%
az consumption budget create \
--budget-name "MonthlyBudgetWithAlerts" \
--amount 1000 \
--category Cost \
--time-grain Monthly \
--start-date $(date -u +%Y-%m-01T00:00:00Z) \
--notifications '[
{
"enabled": true,
"operator": "GreaterThan",
"threshold": 80,
"contactEmails": ["admin@contoso.com"],
"contactRoles": ["Owner", "Contributor"]
},
{
"enabled": true,
"operator": "GreaterThan",
"threshold": 100,
"contactEmails": ["admin@contoso.com", "finance@contoso.com"],
"contactRoles": ["Owner"]
}
]'List Budgets
# List all budgets
az consumption budget list --output table
# Show budget details
az consumption budget show --budget-name "MonthlyBudget"Cost Alerts
# Create action group for cost alerts
az monitor action-group create \
--name "CostAlertActionGroup" \
--resource-group myRG \
--short-name "CostAlert" \
--email admin email=admin@contoso.com
# Create cost alert rule
az monitor metrics alert create \
--name "HighCostAlert" \
--resource-group myRG \
--scopes /subscriptions/{subscription-id} \
--condition "total Cost > 1000" \
--description "Alert when monthly cost exceeds $1000" \
--action "CostAlertActionGroup"Azure Policy for Cost Governance
Enforce Resource SKUs
{
"properties": {
"displayName": "Allowed VM SKUs",
"description": "Restrict VM SKUs to cost-effective options",
"mode": "Indexed",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Compute/virtualMachines"
},
{
"not": {
"field": "Microsoft.Compute/virtualMachines/sku.name",
"in": ["Standard_B2s", "Standard_B2ms", "Standard_D2s_v3", "Standard_D4s_v3"]
}
}
]
},
"then": {
"effect": "deny"
}
}
}
}Require Tags for Cost Tracking
# Assign built-in policy to require tags
az policy assignment create \
--name "RequireCostCenterTag" \
--policy "96670d01-0a4d-4649-9c89-2d3abc0a5025" \
--params '{
"tagName": {
"value": "CostCenter"
}
}' \
--resource-group myRGEnforce Resource Location (Cost Optimization)
# Restrict to cost-effective regions
az policy assignment create \
--name "AllowedLocations" \
--policy "e56962a6-4747-49cd-b67b-bf8b01975c4c" \
--params '{
"listOfAllowedLocations": {
"value": ["eastus", "eastus2", "centralus"]
}
}' \
--resource-group myRGAutomation
Scheduled Cost Reports
#!/bin/bash
# scheduled-cost-report.sh - Run via cron
REPORT_DIR="/reports/azure-costs"
REPORT_FILE="$REPORT_DIR/cost-report-$(date +%Y%m%d).json"
EMAIL="finance@contoso.com"
mkdir -p "$REPORT_DIR"
# Generate cost report
az costmanagement query \
--type ActualCost \
--dataset-aggregation name=Cost,function=Sum \
--dataset-grouping name=ResourceGroup,type=Dimension \
--timeframe MonthToDate > "$REPORT_FILE"
# Parse and format
TOTAL_COST=$(jq -r '.properties.rows | map(.[0]) | add' "$REPORT_FILE")
# Send email (requires mail utility)
cat <<EOF | mail -s "Azure Monthly Cost Report: \$$TOTAL_COST" "$EMAIL"
Azure cost report for $(date +%B\ %Y)
Total Cost: \$$TOTAL_COST
Top 5 Resource Groups:
$(jq -r '.properties.rows | sort_by(.[0]) | reverse | .[0:5] | .[] | "\(.[1]): $\(.[0])"' "$REPORT_FILE")
Full report attached.
EOFAuto-Cleanup Old Resources
#!/bin/bash
# auto-cleanup-old-resources.sh
DAYS_OLD=90
TAG_KEY="ExpirationDate"
echo "Finding resources older than $DAYS_OLD days with expiration dates..."
# Find expired resources
EXPIRED_RESOURCES=$(az resource list \
--query "[?tags.$TAG_KEY != null && tags.$TAG_KEY < '$(date +%Y-%m-%d)'].id" -o tsv)
if [ -z "$EXPIRED_RESOURCES" ]; then
echo "No expired resources found"
exit 0
fi
echo "Found $(echo "$EXPIRED_RESOURCES" | wc -l) expired resources"
# Delete expired resources
echo "$EXPIRED_RESOURCES" | while read -r resource_id; do
echo "Deleting: $resource_id"
az resource delete --ids "$resource_id" --verbose
done
echo "Cleanup complete"Cost Optimization Checklist
- [ ] Review monthly cost reports
- [ ] Identify and delete unused resources (disks, NICs, IPs)
- [ ] Right-size oversized VMs based on utilization metrics
- [ ] Evaluate reserved instances for steady-state workloads
- [ ] Use spot VMs for fault-tolerant batch processing
- [ ] Implement auto-shutdown for dev/test environments
- [ ] Move cold storage to Cool/Archive tiers
- [ ] Consolidate resources to reduce networking costs
- [ ] Review and optimize data transfer patterns
- [ ] Ensure all resources are tagged for cost allocation
- [ ] Set up budgets and cost alerts
- [ ] Enforce cost policies via Azure Policy
- [ ] Regular access reviews to remove unused identities
- [ ] Scale down non-production resources
- [ ] Review licensing (BYOL opportunities)
Related Documentation
- @resource-management.md - Resource tagging for cost tracking
- @cli-patterns.md - Automation scripts for cost management
- @troubleshooting.md - Cost management API issues
- @../examples/environment-setup.md - Cost-optimized environment templates
DevOps Automation with Azure
Comprehensive guide to CI/CD pipelines, infrastructure as code, and GitOps workflows for Azure.
Table of Contents
1. Azure DevOps Pipelines 2. GitHub Actions Integration 3. Infrastructure as Code in CI/CD 4. Deployment Strategies 5. GitOps Workflows 6. Secrets Management
Azure DevOps Pipelines
Azure DevOps provides comprehensive CI/CD capabilities with YAML-based pipelines.
Basic Pipeline Structure
# azure-pipelines.yml
trigger:
branches:
include:
- main
- develop
paths:
exclude:
- docs/**
- README.md
pool:
vmImage: "ubuntu-latest"
variables:
azureSubscription: "MyServiceConnection"
resourceGroup: "myapp-prod-rg"
location: "eastus"
stages:
- stage: Build
displayName: "Build and Test"
jobs:
- job: BuildJob
steps:
- task: AzureCLI@2
displayName: "Validate Bicep Templates"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az bicep build --file infra/main.bicep
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: "infra"
artifactName: "infrastructure"
- stage: Deploy
displayName: "Deploy to Azure"
dependsOn: Build
condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
jobs:
- deployment: DeployInfrastructure
environment: "production"
strategy:
runOnce:
deploy:
steps:
- task: AzureResourceManagerTemplateDeployment@3
displayName: "Deploy Infrastructure"
inputs:
azureResourceManagerConnection: $(azureSubscription)
subscriptionId: $(subscriptionId)
resourceGroupName: $(resourceGroup)
location: $(location)
templateLocation: "Linked artifact"
csmFile: "$(Pipeline.Workspace)/infrastructure/main.bicep"
deploymentMode: "Incremental"Multi-Stage Pipeline with Approvals
# multi-stage-pipeline.yml
stages:
- stage: Build
jobs:
- job: Build
steps:
- script: echo "Building application"
- task: PublishBuildArtifacts@1
- stage: DeployDev
displayName: "Deploy to Development"
dependsOn: Build
jobs:
- deployment: DeployDev
environment: "development"
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
inputs:
azureSubscription: "DevServiceConnection"
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az deployment group create \
--resource-group dev-rg \
--template-file $(Pipeline.Workspace)/infra/main.bicep \
--parameters environment=dev
- stage: DeployProd
displayName: "Deploy to Production"
dependsOn: DeployDev
jobs:
- deployment: DeployProd
environment: "production" # Requires manual approval
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
inputs:
azureSubscription: "ProdServiceConnection"
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az deployment group create \
--resource-group prod-rg \
--template-file $(Pipeline.Workspace)/infra/main.bicep \
--parameters environment=prodPipeline with Testing
stages:
- stage: Test
jobs:
- job: InfrastructureTests
steps:
- task: AzureCLI@2
displayName: "Run Bicep Linting"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
# Install bicep linter
az bicep build --file infra/main.bicep
- task: AzureCLI@2
displayName: "Validate Templates"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az deployment group validate \
--resource-group test-rg \
--template-file infra/main.bicep \
--parameters environment=test
- task: AzureCLI@2
displayName: "Run What-If Analysis"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az deployment group what-if \
--resource-group test-rg \
--template-file infra/main.bicep \
--parameters environment=test
- job: SecurityScanning
steps:
- task: AzureCLI@2
displayName: "Check for Sensitive Data"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
# Check for hardcoded secrets
if grep -r "password\|secret\|apikey" infra/; then
echo "##vso[task.logissue type=error]Found potential secrets in code"
exit 1
fiGitHub Actions Integration
GitHub Actions provides native CI/CD for repositories hosted on GitHub.
Basic Workflow
# .github/workflows/deploy-azure.yml
name: Deploy to Azure
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch: # Manual trigger
env:
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
RESOURCE_GROUP: myapp-prod-rg
LOCATION: eastus
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Validate Bicep
run: |
az bicep build --file infra/main.bicep
- name: Validate Deployment
run: |
az deployment group validate \
--resource-group ${{ env.RESOURCE_GROUP }} \
--template-file infra/main.bicep \
--parameters environment=prod
deploy:
needs: validate
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
environment: production
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy Infrastructure
uses: azure/arm-deploy@v1
with:
subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
resourceGroupName: ${{ env.RESOURCE_GROUP }}
template: ./infra/main.bicep
parameters: environment=prod
deploymentMode: IncrementalMatrix Strategy for Multi-Environment
jobs:
deploy:
runs-on: ubuntu-latest
strategy:
matrix:
environment: [dev, staging, prod]
include:
- environment: dev
resource_group: myapp-dev-rg
approval_required: false
- environment: staging
resource_group: myapp-staging-rg
approval_required: false
- environment: prod
resource_group: myapp-prod-rg
approval_required: true
environment:
name: ${{ matrix.environment }}
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy to ${{ matrix.environment }}
uses: azure/arm-deploy@v1
with:
subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
resourceGroupName: ${{ matrix.resource_group }}
template: ./infra/main.bicep
parameters: environment=${{ matrix.environment }}Reusable Workflows
# .github/workflows/reusable-deploy.yml
name: Reusable Azure Deployment
on:
workflow_call:
inputs:
environment:
required: true
type: string
resource_group:
required: true
type: string
secrets:
AZURE_CREDENTIALS:
required: true
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy Infrastructure
uses: azure/arm-deploy@v1
with:
subscriptionId: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
resourceGroupName: ${{ inputs.resource_group }}
template: ./infra/main.bicep
parameters: environment=${{ inputs.environment }}Usage:
# .github/workflows/deploy-prod.yml
name: Deploy Production
on:
push:
branches: [main]
jobs:
deploy-prod:
uses: ./.github/workflows/reusable-deploy.yml
with:
environment: production
resource_group: myapp-prod-rg
secrets:
AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}Infrastructure as Code in CI/CD
Bicep Deployment Pipeline
# Complete Bicep deployment pipeline
stages:
- stage: Validate
jobs:
- job: ValidateBicep
steps:
- task: AzureCLI@2
displayName: "Bicep Build"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
cd infra
az bicep build --file main.bicep
- task: AzureCLI@2
displayName: "Validate Template"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az deployment sub validate \
--location $(location) \
--template-file infra/main.bicep \
--parameters @infra/parameters/$(environment).json
- task: AzureCLI@2
displayName: "What-If Analysis"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az deployment sub what-if \
--location $(location) \
--template-file infra/main.bicep \
--parameters @infra/parameters/$(environment).json
- stage: Deploy
dependsOn: Validate
jobs:
- deployment: DeployBicep
environment: $(environment)
strategy:
runOnce:
deploy:
steps:
- task: AzureCLI@2
displayName: "Deploy Bicep Template"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az deployment sub create \
--name $(Build.BuildNumber) \
--location $(location) \
--template-file $(Pipeline.Workspace)/infra/main.bicep \
--parameters @$(Pipeline.Workspace)/infra/parameters/$(environment).json
- task: AzureCLI@2
displayName: "Verify Deployment"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
# Check deployment status
STATUS=$(az deployment sub show \
--name $(Build.BuildNumber) \
--query properties.provisioningState -o tsv)
if [ "$STATUS" != "Succeeded" ]; then
echo "Deployment failed with status: $STATUS"
exit 1
fi
echo "✓ Deployment succeeded"Deployment Strategies
Blue-Green Deployment
# Blue-Green deployment with Azure App Service slots
jobs:
- job: BlueGreenDeploy
steps:
- task: AzureCLI@2
displayName: "Deploy to Staging Slot (Green)"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
# Deploy to staging slot
az webapp deployment source config-zip \
--resource-group $(resourceGroup) \
--name $(webAppName) \
--slot staging \
--src $(Build.ArtifactStagingDirectory)/app.zip
- task: AzureCLI@2
displayName: "Warm Up Staging Slot"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
STAGING_URL="https://$(webAppName)-staging.azurewebsites.net"
echo "Warming up $STAGING_URL"
for i in {1..5}; do
curl -f "$STAGING_URL" || exit 1
sleep 2
done
echo "✓ Staging slot is healthy"
- task: AzureCLI@2
displayName: "Swap Slots (Blue ↔ Green)"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az webapp deployment slot swap \
--resource-group $(resourceGroup) \
--name $(webAppName) \
--slot staging \
--target-slot production
echo "✓ Swap completed - Staging is now Production"
- task: AzureCLI@2
displayName: "Verify Production"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
PROD_URL="https://$(webAppName).azurewebsites.net"
curl -f "$PROD_URL" || {
echo "Production health check failed - rolling back"
az webapp deployment slot swap \
--resource-group $(resourceGroup) \
--name $(webAppName) \
--slot staging \
--target-slot production
exit 1
}
echo "✓ Production is healthy"Canary Deployment
# Canary deployment with gradual traffic shift
jobs:
- job: CanaryDeploy
steps:
- task: AzureCLI@2
displayName: "Deploy Canary Version"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
# Deploy to canary slot
az webapp deployment source config-zip \
--resource-group $(resourceGroup) \
--name $(webAppName) \
--slot canary \
--src $(Build.ArtifactStagingDirectory)/app.zip
- task: AzureCLI@2
displayName: "Route 10% Traffic to Canary"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az webapp traffic-routing set \
--resource-group $(resourceGroup) \
--name $(webAppName) \
--distribution canary=10
echo "✓ 10% traffic routed to canary"
- task: ManualValidation@0
displayName: "Validate Canary Metrics"
inputs:
instructions: "Check monitoring dashboards for canary performance and errors"
- task: AzureCLI@2
displayName: "Increase to 50% Traffic"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
az webapp traffic-routing set \
--resource-group $(resourceGroup) \
--name $(webAppName) \
--distribution canary=50
- task: ManualValidation@0
displayName: "Final Validation"
- task: AzureCLI@2
displayName: "Complete Canary Rollout"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
# Swap canary to production
az webapp deployment slot swap \
--resource-group $(resourceGroup) \
--name $(webAppName) \
--slot canary \
--target-slot production
# Clear traffic routing
az webapp traffic-routing clear \
--resource-group $(resourceGroup) \
--name $(webAppName)GitOps Workflows
ArgoCD with Azure
# Flux CD configuration for Azure resources
# .flux/infrastructure.yaml
apiVersion: kustomize.toolkit.fluxcd.io/v1
kind: Kustomization
metadata:
name: azure-infrastructure
namespace: flux-system
spec:
interval: 10m
path: ./infrastructure/azure
prune: true
sourceRef:
kind: GitRepository
name: infrastructure
validation: client
healthChecks:
- apiVersion: apps/v1
kind: Deployment
name: myapp
namespace: productionSecrets Management
Using Azure Key Vault in Pipelines
steps:
- task: AzureKeyVault@2
displayName: "Retrieve Secrets from Key Vault"
inputs:
azureSubscription: $(azureSubscription)
keyVaultName: "myapp-keyvault"
secretsFilter: "*"
runAsPreJob: true
- task: AzureCLI@2
displayName: "Use Secrets in Deployment"
inputs:
azureSubscription: $(azureSubscription)
scriptType: "bash"
scriptLocation: "inlineScript"
inlineScript: |
# Secrets available as pipeline variables
az webapp config appsettings set \
--resource-group $(resourceGroup) \
--name $(webAppName) \
--settings DatabasePassword=$(DatabasePassword)GitHub Actions with Key Vault
steps:
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Get Secrets from Key Vault
uses: azure/get-keyvault-secrets@v1
with:
keyvault: "myapp-keyvault"
secrets: "DatabasePassword, ApiKey"
id: keyvault
- name: Use Secrets
run: |
echo "Database password retrieved"
# Use ${{ steps.keyvault.outputs.DatabasePassword }}Related Documentation
- @resource-management.md - Infrastructure as code fundamentals
- @cli-patterns.md - Scripting patterns for automation
- @troubleshooting.md - Pipeline debugging
- @../examples/environment-setup.md - Complete environment automation
Azure MCP Integration
Guide to integrating Azure MCP (Model Context Protocol) server with Claude Code for AI-powered Azure operations.
Table of Contents
1. Overview 2. Installation and Setup 3. Available Tools 4. Usage Patterns 5. Advanced Workflows 6. Troubleshooting
Overview
Azure MCP provides a standardized interface for AI applications to interact with Azure services. It exposes Azure management operations as MCP tools that Claude Code can invoke directly.
Benefits
- Natural language Azure operations: "Show me all VMs" instead of complex CLI commands
- Contextual awareness: MCP maintains session context across multiple operations
- Error handling: Automatic retry and fallback mechanisms
- Type safety: Validated inputs and structured outputs
- Multi-operation workflows: Compose complex operations from simple tools
Architecture
Claude Code
↓
MCP Protocol
↓
Azure MCP Server
↓
Azure CLI / SDK
↓
Azure APIsInstallation and Setup
Prerequisites
# Verify Node.js 18+ installed
node --version # Should be >= 18.0.0
# Verify Azure CLI authenticated
az account showInstall Azure MCP Server
# Global installation (recommended)
npm install -g @modelcontextprotocol/server-azure
# Verify installation
npx @modelcontextprotocol/server-azure --versionConfigure Claude Code
Add to ~/.config/claude-code/mcp.json:
{
"mcpServers": {
"azure": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-azure"],
"env": {
"AZURE_SUBSCRIPTION_ID": "your-subscription-id",
"AZURE_TENANT_ID": "your-tenant-id"
}
}
}
}Get Subscription and Tenant IDs
# Get subscription ID
az account show --query id -o tsv
# Get tenant ID
az account show --query tenantId -o tsv
# List all subscriptions
az account list --query "[].{Name:name, ID:id, TenantID:tenantId}" --output tableVerify MCP Configuration
Restart Claude Code and test:
List all my Azure resource groupsIf MCP is configured correctly, Claude Code will use the azure_list_resources tool instead of running az commands directly.
Available Tools
Resource Management Tools
azure_list_resources
- Purpose: List resources in subscription or resource group
- Parameters:
resourceGroup(optional): Filter by resource groupresourceType(optional): Filter by resource typelocation(optional): Filter by locationtags(optional): Filter by tags
Example usage:
Show all VMs in my subscription
Show storage accounts in resource group "production-rg"
List all resources tagged with Environment=Productionazure_get_resource
- Purpose: Get detailed information about a specific resource
- Parameters:
resourceId: Full resource ID or resource nameresourceGroup: Resource group name (if using resource name)resourceType: Resource type (if using resource name)
Example usage:
Show details for VM named "myVM" in resource group "myRG"
Get information about resource ID /subscriptions/.../virtualMachines/myVMazure_create_resource
- Purpose: Create a new Azure resource
- Parameters:
resourceGroup: Target resource groupresourceType: Type of resource to createname: Resource namelocation: Azure regionproperties: Resource-specific properties (JSON)
Example usage:
Create a storage account named "mystorageaccount" in resource group "myRG" in eastus
Create a virtual network named "myVNet" with address space 10.0.0.0/16azure_delete_resource
- Purpose: Delete an existing resource
- Parameters:
resourceId: Full resource IDnoWait(optional): Don't wait for deletion to complete
Example usage:
Delete the VM named "testVM" in resource group "dev-rg"
Remove all resources tagged with Environment=TemporaryIdentity and Access Tools
azure_list_users
- Purpose: List Entra ID users
- Parameters:
filter(optional): OData filter expressiontop(optional): Limit results
Example usage:
List all users in Entra ID
Show users in the Engineering department
Find users with email starting with "john"azure_get_user
- Purpose: Get detailed user information
- Parameters:
userId: User principal name or object ID
Example usage:
Show details for user jane@contoso.com
Get information about user with ID 12345678-1234-1234-1234-123456789012azure_list_service_principals
- Purpose: List service principals
- Parameters:
filter(optional): OData filter expression
Example usage:
List all service principals
Show service principals for my applicationazure_list_role_assignments
- Purpose: List RBAC role assignments
- Parameters:
scope(optional): Limit to specific scopeprincipalId(optional): Filter by principalroleDefinitionName(optional): Filter by role
Example usage:
List all role assignments in my subscription
Show role assignments for user jane@contoso.com
Find all Owner role assignmentsQuery Tools
azure_query
- Purpose: Execute Azure Resource Graph queries
- Parameters:
query: KQL (Kusto Query Language) query string
Example usage:
Query all VMs with their power state
Find resources created in the last 7 days
Show cost by resource group for this monthazure_cli
- Purpose: Execute arbitrary az CLI commands
- Parameters:
command: CLI command to execute (without "az" prefix)
Example usage:
Run: az vm list --query "[?powerState=='VM running']"
Execute: az account showUsage Patterns
Pattern 1: Resource Discovery
# Natural language → MCP tool invocation
User: "Show me all my storage accounts"
Claude Code uses azure_list_resources:
{
"tool": "azure_list_resources",
"parameters": {
"resourceType": "Microsoft.Storage/storageAccounts"
}
}
# Response formatted for user
Found 3 storage accounts:
- mystorageaccount (eastus, Standard_LRS)
- prodstorageaccount (westus, Premium_LRS)
- backupstorage (centralus, Standard_GRS)Pattern 2: Multi-Step Operations
User: "Create a new VM in resource group 'dev-rg' and assign me Reader access"
Step 1: azure_create_resource (create VM)
Step 2: azure_list_users (find user)
Step 3: azure_cli (assign role)
Complete workflow automated by Claude CodePattern 3: Compliance Checking
User: "Find all storage accounts without encryption enabled"
Step 1: azure_list_resources (get all storage accounts)
Step 2: azure_get_resource (check each for encryption property)
Step 3: Format results showing non-compliant accountsPattern 4: Cost Analysis
User: "What are my top 5 most expensive resources this month?"
Step 1: azure_query (Resource Graph query for cost data)
Step 2: Aggregate and sort by cost
Step 3: Present formatted table with costsAdvanced Workflows
Automated Resource Tagging
# Claude Code can compose this workflow using MCP tools
# 1. Get all untagged resources
untagged_resources = azure_list_resources(filter="tags eq null")
# 2. For each resource, infer tags based on naming convention
for resource in untagged_resources:
tags = infer_tags_from_name(resource.name)
# 3. Apply tags
azure_cli(f"resource tag --tags {tags} --ids {resource.id}")
# 4. Generate report
print(f"Tagged {len(untagged_resources)} resources")Automated Access Review
# Complete access review workflow
# 1. List all role assignments
assignments = azure_list_role_assignments()
# 2. For each Owner/Contributor role
high_privilege = [a for a in assignments if a.role in ['Owner', 'Contributor']]
# 3. Check last sign-in for each user
for assignment in high_privilege:
user = azure_get_user(assignment.principalId)
if days_since_last_signin(user) > 90:
# Flag for review
report_stale_access(assignment)
# 4. Generate compliance reportEnvironment Provisioning
User: "Setup a complete dev environment for the new project"
Claude Code orchestrates:
1. azure_create_resource: Resource group
2. azure_create_resource: Virtual network
3. azure_create_resource: Storage account
4. azure_create_resource: App Service Plan
5. azure_create_resource: App Service
6. azure_create_resource: SQL Database
7. azure_create_resource: Key Vault
8. azure_cli: Configure networking
9. azure_cli: Apply tags
10. azure_list_role_assignments: Grant team access
Complete environment ready in minutesResource Cleanup
User: "Delete all dev resources older than 30 days"
Claude Code workflow:
1. azure_query: Find dev resources by tag and creation date
2. Confirm with user (list resources to delete)
3. azure_delete_resource: Delete each resource (parallel)
4. Report: Resources deleted, estimated cost savingsTroubleshooting
MCP Server Not Responding
Symptom: Claude Code doesn't use Azure MCP tools
Solutions:
# 1. Verify MCP server installation
npx @modelcontextprotocol/server-azure --version
# 2. Check MCP configuration file
cat ~/.config/claude-code/mcp.json
# 3. Verify Azure CLI authentication
az account show
# 4. Check subscription ID in config matches current subscription
az account show --query id -o tsv
# 5. Restart Claude CodeAuthentication Errors
Symptom: "Authentication failed" or "Unauthorized"
Solutions:
# Re-authenticate Azure CLI
az logout
az login
# Verify subscription access
az account list --output table
# Check environment variables in MCP config
echo $AZURE_SUBSCRIPTION_ID
echo $AZURE_TENANT_ID
# Update MCP config with correct valuesPermission Denied
Symptom: "Insufficient privileges" errors
Solutions:
# Check your role assignments
az role assignment list \
--assignee $(az ad signed-in-user show --query id -o tsv) \
--all
# Verify you have at least Reader role at subscription level
# Request appropriate access from subscription administratorTool Not Found
Symptom: "Tool azure_list_resources not found"
Solutions:
1. Verify MCP server version: npm list -g @modelcontextprotocol/server-azure 2. Update to latest: npm update -g @modelcontextprotocol/server-azure 3. Check MCP server logs for errors 4. Restart Claude Code
Slow Performance
Symptom: MCP operations take a long time
Optimizations:
1. Use filters: Always filter queries to reduce data transfer 2. Cache results: MCP maintains session cache automatically 3. Parallel operations: Request multiple resources simultaneously 4. Specific queries: Use Resource Graph queries instead of listing all resources
# Slow
"List all resources and find VMs"
# Fast
"List all VMs" (uses resourceType filter)Debugging MCP Requests
Enable debug logging:
{
"mcpServers": {
"azure": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-azure"],
"env": {
"AZURE_SUBSCRIPTION_ID": "your-subscription-id",
"AZURE_TENANT_ID": "your-tenant-id",
"DEBUG": "mcp:*"
}
}
}
}View logs in Claude Code console.
Best Practices
1. Use natural language: Let Claude Code translate to MCP tools 2. Be specific: "VMs in production-rg" vs "show me stuff" 3. Confirm destructive operations: MCP will ask before deleting 4. Leverage multi-step workflows: Combine operations for complex tasks 5. Use filters: Narrow results for faster responses 6. Check permissions first: Verify access before attempting operations
Related Documentation
- @user-management.md - Identity operations via MCP
- @role-assignments.md - RBAC through MCP tools
- @resource-management.md - Resource lifecycle with MCP
- @cli-patterns.md - When to use CLI vs MCP
- @../examples/mcp-workflow.md - Complete MCP workflow examples
Resource Management in Azure
Comprehensive guide to managing Azure resources, resource groups, subscriptions, and infrastructure as code.
Table of Contents
1. Resource Hierarchy 2. Resource Groups 3. Resource Operations 4. Infrastructure as Code 5. Resource Tagging 6. Resource Locks 7. Resource Move Operations
Resource Hierarchy
Azure organizes resources in a hierarchical structure:
Management Groups (optional)
└── Subscriptions (billing boundary)
└── Resource Groups (logical container)
└── Resources (VMs, storage, networks, etc.)Management Groups
Group multiple subscriptions for policy and compliance management.
# Create management group
az account management-group create \
--name "ProductionManagementGroup" \
--display-name "Production Management Group"
# Add subscription to management group
az account management-group subscription add \
--name "ProductionManagementGroup" \
--subscription {subscription-id}
# List management groups
az account management-group list --output table
# Show management group hierarchy
az account management-group show \
--name "ProductionManagementGroup" \
--expand --recurseSubscriptions
# List subscriptions
az account list --output table
# Show current subscription
az account show
# Set active subscription
az account set --subscription "My Subscription"
# Get subscription ID
az account show --query id -o tsv
# List available locations
az account list-locations --query "[].{Name:name, DisplayName:displayName}" --output tableResource Groups
Resource groups are fundamental containers for managing related Azure resources.
Creating Resource Groups
# Basic creation
az group create --name myResourceGroup --location eastus
# With tags
az group create \
--name myResourceGroup \
--location eastus \
--tags Environment=Production Department=IT CostCenter=12345
# Multiple resource groups (for multi-region)
for region in eastus westus centralus; do
az group create --name "myapp-${region}-rg" --location "$region"
doneListing and Querying Resource Groups
# List all resource groups
az group list --output table
# Filter by location
az group list --query "[?location=='eastus']" --output table
# Filter by tag
az group list --query "[?tags.Environment=='Production']" --output table
# Show resources in group
az resource list --resource-group myResourceGroup --output table
# Count resources per group
az group list --query "[].{Name:name, Count:length(resources)}"Updating Resource Groups
# Add/update tags
az group update \
--name myResourceGroup \
--tags Environment=Production Owner=jane@contoso.com
# Add tags without removing existing
az group update \
--name myResourceGroup \
--set tags.NewTag=NewValueDeleting Resource Groups
# Delete resource group (deletes ALL resources inside)
az group delete --name myResourceGroup --yes --no-wait
# Delete with confirmation
az group delete --name myResourceGroup
# Delete multiple resource groups
for rg in myRG1 myRG2 myRG3; do
az group delete --name "$rg" --yes --no-wait
done
# Check deletion status
az group exists --name myResourceGroupResource Group Best Practices
1. Organize by lifecycle - Resources that share the same lifecycle belong together 2. One application per group - Group all resources for a single application 3. Environment separation - Separate dev, test, prod into different groups 4. Consistent naming - Use naming convention: {app}-{env}-{region}-rg 5. Tag everything - Apply tags for cost tracking, ownership, environment 6. Use locks - Protect production resource groups from accidental deletion 7. Same region preference - Resources in same region as group for better management
Resource Operations
Listing Resources
# List all resources in subscription
az resource list --output table
# List resources in resource group
az resource list --resource-group myResourceGroup --output table
# Filter by resource type
az resource list --resource-type "Microsoft.Compute/virtualMachines" --output table
# Filter by location
az resource list --location eastus --output table
# Filter by tag
az resource list --tag Environment=Production --output table
# Get specific resource
az resource show \
--resource-group myResourceGroup \
--name myVM \
--resource-type "Microsoft.Compute/virtualMachines"Creating Resources
# Create virtual network
az network vnet create \
--resource-group myResourceGroup \
--name myVNet \
--address-prefix 10.0.0.0/16 \
--subnet-name default \
--subnet-prefix 10.0.1.0/24
# Create storage account
az storage account create \
--name mystorageaccount \
--resource-group myResourceGroup \
--location eastus \
--sku Standard_LRS \
--kind StorageV2
# Create virtual machine
az vm create \
--resource-group myResourceGroup \
--name myVM \
--image Ubuntu2204 \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keysUpdating Resources
# Update VM size
az vm resize \
--resource-group myResourceGroup \
--name myVM \
--size Standard_D4s_v3
# Update storage account tier
az storage account update \
--name mystorageaccount \
--resource-group myResourceGroup \
--sku Standard_GRS
# Update resource tags
az resource tag \
--tags Environment=Production CostCenter=12345 \
--ids /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/myVMDeleting Resources
# Delete virtual machine
az vm delete --resource-group myResourceGroup --name myVM --yes
# Delete multiple resources
for vm in vm1 vm2 vm3; do
az vm delete --resource-group myResourceGroup --name "$vm" --yes --no-wait
done
# Delete all resources of a type
az resource list \
--resource-group myResourceGroup \
--resource-type "Microsoft.Compute/virtualMachines" \
--query "[].id" -o tsv | \
xargs -I {} az resource delete --ids {}Infrastructure as Code
ARM Templates
ARM templates are JSON files that define infrastructure declaratively.
Basic template structure:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "mystorageaccount",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2"
}
],
"outputs": {
"storageAccountId": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', 'mystorageaccount')]"
}
}
}Deploy ARM template:
az deployment group create \
--resource-group myResourceGroup \
--template-file template.json \
--parameters location=eastus
# With parameter file
az deployment group create \
--resource-group myResourceGroup \
--template-file template.json \
--parameters @parameters.json
# Validate before deploying
az deployment group validate \
--resource-group myResourceGroup \
--template-file template.json
# What-if analysis
az deployment group what-if \
--resource-group myResourceGroup \
--template-file template.jsonBicep
Bicep is a domain-specific language (DSL) for deploying Azure resources with cleaner syntax than ARM templates.
Basic Bicep file:
param location string = resourceGroup().location
param storageAccountName string = 'mystorageaccount'
param storageSku string = 'Standard_LRS'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: {
name: storageSku
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
}
}
output storageAccountId string = storageAccount.id
output primaryEndpoint string = storageAccount.properties.primaryEndpoints.blobMulti-resource Bicep example:
param location string = resourceGroup().location
param vmName string
param vmSize string = 'Standard_B2s'
param adminUsername string
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
name: '${vmName}-vnet'
location: location
properties: {
addressSpace: {
addressPrefixes: ['10.0.0.0/16']
}
subnets: [
{
name: 'default'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
]
}
}
resource nic 'Microsoft.Network/networkInterfaces@2023-05-01' = {
name: '${vmName}-nic'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
subnet: {
id: vnet.properties.subnets[0].id
}
privateIPAllocationMethod: 'Dynamic'
}
}
]
}
}
resource vm 'Microsoft.Compute/virtualMachines@2023-07-01' = {
name: vmName
location: location
properties: {
hardwareProfile: {
vmSize: vmSize
}
osProfile: {
computerName: vmName
adminUsername: adminUsername
linuxConfiguration: {
disablePasswordAuthentication: true
ssh: {
publicKeys: [
{
path: '/home/${adminUsername}/.ssh/authorized_keys'
keyData: loadTextContent('~/.ssh/id_rsa.pub')
}
]
}
}
}
networkProfile: {
networkInterfaces: [
{
id: nic.id
}
]
}
storageProfile: {
imageReference: {
publisher: 'Canonical'
offer: '0001-com-ubuntu-server-jammy'
sku: '22_04-lts-gen2'
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'Premium_LRS'
}
}
}
}
}Bicep CLI operations:
# Install/update Bicep
az bicep install
az bicep upgrade
az bicep version
# Build Bicep to ARM template
az bicep build --file main.bicep
# Decompile ARM template to Bicep
az bicep decompile --file template.json
# Deploy Bicep file
az deployment group create \
--resource-group myResourceGroup \
--template-file main.bicep \
--parameters vmName=myVM adminUsername=azureuser
# Validate Bicep
az deployment group validate \
--resource-group myResourceGroup \
--template-file main.bicepBicep Modules
Organize reusable infrastructure patterns:
storage-module.bicep:
param location string
param storageAccountName string
param storageSku string = 'Standard_LRS'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountName
location: location
sku: {
name: storageSku
}
kind: 'StorageV2'
}
output storageAccountId string = storageAccount.idmain.bicep (using module):
param location string = resourceGroup().location
module storage 'storage-module.bicep' = {
name: 'storageDeployment'
params: {
location: location
storageAccountName: 'mystorageaccount'
storageSku: 'Standard_GRS'
}
}
output storageId string = storage.outputs.storageAccountIdResource Tagging
Tags enable organization, cost tracking, and automation.
Tagging Strategy
Common tag schemas:
{
"Environment": "Production|Development|Test",
"CostCenter": "IT|Engineering|Marketing",
"Owner": "email@contoso.com",
"Application": "CustomerPortal|InternalTools",
"Criticality": "High|Medium|Low",
"Compliance": "PCI-DSS|HIPAA|SOC2",
"BackupPolicy": "Daily|Weekly|None",
"MaintenanceWindow": "Saturday 2-4am UTC",
"ExpirationDate": "2025-12-31"
}Applying Tags
# Tag resource group
az group update \
--name myResourceGroup \
--tags Environment=Production CostCenter=IT Owner=admin@contoso.com
# Tag resource
az resource tag \
--tags Environment=Production Application=WebApp \
--ids /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/myVM
# Bulk tag resources in resource group
az resource list --resource-group myResourceGroup --query "[].id" -o tsv | \
xargs -I {} az resource tag --tags Environment=Production --ids {}
# Copy tags from resource group to resources
RG_TAGS=$(az group show --name myResourceGroup --query tags -o json)
az resource list --resource-group myResourceGroup --query "[].id" -o tsv | \
xargs -I {} az resource tag --tags "$RG_TAGS" --ids {}Querying by Tags
# Find resources by tag
az resource list --tag Environment=Production --output table
# Find resources with multiple tag criteria
az resource list \
--query "[?tags.Environment=='Production' && tags.Criticality=='High']" \
--output table
# List all tag keys/values
az tag list --output tableResource Locks
Locks prevent accidental deletion or modification of resources.
Lock Types
- CanNotDelete: Can read and modify, but cannot delete
- ReadOnly: Can only read, no modifications or deletions
Applying Locks
# Lock resource group
az lock create \
--name DontDeleteLock \
--lock-type CanNotDelete \
--resource-group myResourceGroup
# Lock specific resource
az lock create \
--name ReadOnlyLock \
--lock-type ReadOnly \
--resource-group myResourceGroup \
--resource-name myVM \
--resource-type Microsoft.Compute/virtualMachines
# Lock at subscription level
az lock create \
--name SubscriptionLock \
--lock-type CanNotDelete \
--resource-group myResourceGroupManaging Locks
# List locks
az lock list --resource-group myResourceGroup --output table
# Show lock details
az lock show --name DontDeleteLock --resource-group myResourceGroup
# Delete lock
az lock delete --name DontDeleteLock --resource-group myResourceGroupResource Move Operations
Move resources between resource groups or subscriptions.
Move Between Resource Groups
# Get resource IDs to move
RESOURCE_IDS=$(az resource list --resource-group sourceRG --query "[].id" -o tsv)
# Move resources
az resource move \
--destination-group targetRG \
--ids $RESOURCE_IDS
# Move specific resources
az resource move \
--destination-group targetRG \
--ids /subscriptions/{sub}/resourceGroups/sourceRG/providers/Microsoft.Compute/virtualMachines/myVMMove Between Subscriptions
az resource move \
--destination-group targetRG \
--destination-subscription-id {target-subscription-id} \
--ids {resource-ids}Move Limitations
Not all resources support move operations. Check compatibility:
az rest --method POST \
--url "https://management.azure.com/subscriptions/{sub}/resourceGroups/{rg}/validateMoveResources?api-version=2021-04-01" \
--body '{"resources": ["{resource-id}"], "targetResourceGroup": "{target-rg-id}"}'Related Documentation
- @user-management.md - Identity and access management
- @role-assignments.md - RBAC for resources
- @cli-patterns.md - Advanced CLI patterns for resource management
- @devops-automation.md - Infrastructure as code in CI/CD
- @cost-optimization.md - Resource cost management
- @../examples/environment-setup.md - Complete environment provisioning
Role Assignments and RBAC in Azure
Comprehensive guide to Role-Based Access Control (RBAC) in Azure, including built-in roles, custom roles, scope management, and access reviews.
Table of Contents
1. RBAC Fundamentals 2. Built-in Roles 3. Role Assignment Operations 4. Custom Roles 5. Scope Management 6. Access Reviews and Auditing 7. Troubleshooting
RBAC Fundamentals
Azure RBAC is an authorization system that provides fine-grained access management for Azure resources.
Core Components
Security Principal (Who):
- User: Individual with Entra ID account
- Group: Collection of users
- Service Principal: Identity for applications/services
- Managed Identity: Azure-managed service principal
Role Definition (What):
- Collection of permissions (actions and data actions)
- Examples: Owner, Contributor, Reader, custom roles
Scope (Where):
- Management Group: Multiple subscriptions
- Subscription: Billing boundary
- Resource Group: Logical container
- Resource: Individual service
RBAC Assignment Formula
Security Principal + Role Definition + Scope = Role AssignmentExample:
jane@contoso.com + Contributor + /subscriptions/{sub}/resourceGroups/myRG
= Jane has Contributor access to myRG resource groupPermission Model
Actions: Control plane operations (management operations)
Microsoft.Compute/virtualMachines/read
Microsoft.Compute/virtualMachines/write
Microsoft.Compute/virtualMachines/deleteDataActions: Data plane operations (data access)
Microsoft.Storage/storageAccounts/blobServices/containers/blobs/read
Microsoft.Storage/storageAccounts/blobServices/containers/blobs/writeNotActions: Excluded from allowed actions NotDataActions: Excluded from allowed data actions
Built-in Roles
Azure provides 100+ built-in roles. Here are the most commonly used:
Fundamental Roles
Owner
- Full access to all resources
- Can assign roles to others
- Scope: All levels
az role assignment create \
--assignee jane@contoso.com \
--role Owner \
--scope /subscriptions/{subscription-id}Contributor
- Create and manage all types of resources
- Cannot assign roles
- Cannot manage Microsoft Entra directory
az role assignment create \
--assignee jane@contoso.com \
--role Contributor \
--scope /subscriptions/{subscription-id}/resourceGroups/myRGReader
- View all resources
- No modification permissions
az role assignment create \
--assignee jane@contoso.com \
--role Reader \
--scope /subscriptions/{subscription-id}User Access Administrator
- Manage user access to Azure resources
- Cannot manage resources themselves
az role assignment create \
--assignee jane@contoso.com \
--role "User Access Administrator" \
--scope /subscriptions/{subscription-id}Compute Roles
Virtual Machine Contributor
- Manage VMs but not access to them
- Cannot manage virtual network or storage account
az role assignment create \
--assignee jane@contoso.com \
--role "Virtual Machine Contributor" \
--scope /subscriptions/{sub}/resourceGroups/{rg}Virtual Machine Administrator Login
- View VMs in portal and login as administrator
az role assignment create \
--assignee jane@contoso.com \
--role "Virtual Machine Administrator Login" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{vm}Virtual Machine User Login
- View VMs and login as regular user
az role assignment create \
--assignee jane@contoso.com \
--role "Virtual Machine User Login" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/{vm}Storage Roles
Storage Account Contributor
- Manage storage accounts (control plane)
- Cannot access data
az role assignment create \
--assignee jane@contoso.com \
--role "Storage Account Contributor" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{storage}Storage Blob Data Contributor
- Read, write, delete blob containers and data
az role assignment create \
--assignee jane@contoso.com \
--role "Storage Blob Data Contributor" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{storage}Storage Blob Data Reader
- Read blob containers and data
az role assignment create \
--assignee jane@contoso.com \
--role "Storage Blob Data Reader" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{storage}Networking Roles
Network Contributor
- Manage networks but not access to them
az role assignment create \
--assignee jane@contoso.com \
--role "Network Contributor" \
--scope /subscriptions/{sub}/resourceGroups/{rg}Database Roles
SQL DB Contributor
- Manage SQL databases but not access to them
- Cannot manage security policies
az role assignment create \
--assignee jane@contoso.com \
--role "SQL DB Contributor" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Sql/servers/{server}SQL Security Manager
- Manage security policies of SQL servers and databases
az role assignment create \
--assignee jane@contoso.com \
--role "SQL Security Manager" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Sql/servers/{server}Security Roles
Security Admin
- View and update security policies
- View security alerts and recommendations
az role assignment create \
--assignee jane@contoso.com \
--role "Security Admin" \
--scope /subscriptions/{subscription-id}Security Reader
- View security recommendations and alerts
- Cannot update security policies
az role assignment create \
--assignee jane@contoso.com \
--role "Security Reader" \
--scope /subscriptions/{subscription-id}Key Vault Roles
Key Vault Administrator
- Perform all data plane operations (keys, secrets, certificates)
az role assignment create \
--assignee jane@contoso.com \
--role "Key Vault Administrator" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/{vault}Key Vault Secrets User
- Read secret contents
az role assignment create \
--assignee jane@contoso.com \
--role "Key Vault Secrets User" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/{vault}Role Assignment Operations
Creating Role Assignments
Assign to user:
az role assignment create \
--assignee jane@contoso.com \
--role Contributor \
--scope /subscriptions/{sub}/resourceGroups/myRGAssign to group:
# Get group object ID
GROUP_ID=$(az ad group show --group "Engineering Team" --query id -o tsv)
az role assignment create \
--assignee "$GROUP_ID" \
--role Contributor \
--scope /subscriptions/{sub}/resourceGroups/myRGAssign to service principal:
az role assignment create \
--assignee {app-id} \
--role Reader \
--scope /subscriptions/{sub}Assign to managed identity:
# Get managed identity principal ID
PRINCIPAL_ID=$(az vm show --name myVM --resource-group myRG --query identity.principalId -o tsv)
az role assignment create \
--assignee "$PRINCIPAL_ID" \
--role "Storage Blob Data Reader" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{storage}Listing Role Assignments
List all assignments for a user:
az role assignment list --assignee jane@contoso.com --all --output tableList assignments at specific scope:
az role assignment list --scope /subscriptions/{sub}/resourceGroups/myRG --output tableList assignments for a resource:
az role assignment list \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/myVM \
--output tableList assignments including inherited:
az role assignment list \
--scope /subscriptions/{sub}/resourceGroups/myRG \
--include-inherited \
--output tableQuery specific role:
az role assignment list \
--role Contributor \
--query "[].{Principal:principalName, Scope:scope}" \
--output tableList with classic administrators:
az role assignment list --all --include-classic-administrators --output tableDeleting Role Assignments
Delete specific assignment:
az role assignment delete \
--assignee jane@contoso.com \
--role Contributor \
--scope /subscriptions/{sub}/resourceGroups/myRGDelete by assignment ID:
# Get assignment ID
ASSIGNMENT_ID=$(az role assignment list \
--assignee jane@contoso.com \
--role Contributor \
--scope /subscriptions/{sub}/resourceGroups/myRG \
--query "[0].id" -o tsv)
az role assignment delete --ids "$ASSIGNMENT_ID"Bulk delete (remove user from all roles):
az role assignment list --assignee jane@contoso.com --all --query "[].id" -o tsv | \
xargs -I {} az role assignment delete --ids {}Custom Roles
Create custom roles when built-in roles don't meet your specific requirements.
Creating Custom Roles
Define role in JSON file (custom-role.json):
{
"Name": "Virtual Machine Operator",
"IsCustom": true,
"Description": "Can monitor and restart virtual machines",
"Actions": [
"Microsoft.Compute/*/read",
"Microsoft.Compute/virtualMachines/start/action",
"Microsoft.Compute/virtualMachines/restart/action",
"Microsoft.Resources/subscriptions/resourceGroups/read",
"Microsoft.Insights/alertRules/*",
"Microsoft.Support/*"
],
"NotActions": [],
"DataActions": [],
"NotDataActions": [],
"AssignableScopes": ["/subscriptions/{subscription-id}"]
}Create role:
az role definition create --role-definition custom-role.jsonCustom Role Examples
Storage Account Key Reader:
{
"Name": "Storage Account Key Reader",
"IsCustom": true,
"Description": "Read storage account keys",
"Actions": [
"Microsoft.Storage/storageAccounts/read",
"Microsoft.Storage/storageAccounts/listkeys/action"
],
"NotActions": [],
"AssignableScopes": ["/subscriptions/{subscription-id}"]
}VM Snapshot Creator:
{
"Name": "VM Snapshot Creator",
"IsCustom": true,
"Description": "Create and manage VM snapshots",
"Actions": [
"Microsoft.Compute/disks/read",
"Microsoft.Compute/snapshots/*",
"Microsoft.Resources/subscriptions/resourceGroups/read"
],
"NotActions": [],
"AssignableScopes": ["/subscriptions/{subscription-id}/resourceGroups/production-rg"]
}Cost Reader with Export:
{
"Name": "Cost Management Analyst",
"IsCustom": true,
"Description": "View costs and export data",
"Actions": [
"Microsoft.CostManagement/*/read",
"Microsoft.CostManagement/exports/*",
"Microsoft.Consumption/*/read"
],
"NotActions": [],
"AssignableScopes": ["/subscriptions/{subscription-id}"]
}Managing Custom Roles
List custom roles:
az role definition list --custom-role-only true --output tableShow role definition:
az role definition list --name "Virtual Machine Operator"Update custom role:
# Modify the JSON file, then:
az role definition update --role-definition custom-role.jsonDelete custom role:
az role definition delete --name "Virtual Machine Operator"Custom Role Best Practices
1. Start with built-in role - Clone and modify 2. Use wildcards sparingly - Be explicit with permissions 3. Document thoroughly - Clear description and purpose 4. Test in non-production - Validate permissions before prod use 5. Limit AssignableScopes - Restrict to necessary subscriptions/resource groups 6. Regular reviews - Audit and update as needed 7. Version control - Store JSON definitions in Git
Scope Management
Scope Hierarchy
Management Group (optional)
└── Subscription
└── Resource Group
└── ResourcePermissions inherit down the hierarchy. Assignment at higher scope automatically applies to lower scopes.
Scope Patterns
Subscription-wide access:
az role assignment create \
--assignee jane@contoso.com \
--role Contributor \
--scope /subscriptions/{subscription-id}Resource group access:
az role assignment create \
--assignee jane@contoso.com \
--role Contributor \
--scope /subscriptions/{sub}/resourceGroups/myRGSpecific resource access:
az role assignment create \
--assignee jane@contoso.com \
--role "Virtual Machine Contributor" \
--scope /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Compute/virtualMachines/myVMManagement group access:
az role assignment create \
--assignee jane@contoso.com \
--role Reader \
--scope /providers/Microsoft.Management/managementGroups/{management-group-id}Multi-Scope Strategies
Environment-based:
# Development - full access
az role assignment create \
--assignee dev-team@contoso.com \
--role Contributor \
--scope /subscriptions/{dev-sub}
# Production - limited access
az role assignment create \
--assignee dev-team@contoso.com \
--role Reader \
--scope /subscriptions/{prod-sub}Application-based:
# Frontend team - web resources only
az role assignment create \
--assignee frontend-team@contoso.com \
--role "Website Contributor" \
--scope /subscriptions/{sub}/resourceGroups/frontend-rg
# Backend team - compute and database
az role assignment create \
--assignee backend-team@contoso.com \
--role Contributor \
--scope /subscriptions/{sub}/resourceGroups/backend-rgAccess Reviews and Auditing
Regular Access Reviews
List all role assignments:
az role assignment list --all --output table > access-review-$(date +%Y%m%d).txtFind high-privilege assignments:
az role assignment list \
--role Owner \
--all \
--query "[].{Principal:principalName, Scope:scope, Type:principalType}" \
--output tableFind assignments for specific resource type:
az role assignment list \
--all \
--query "[?contains(scope, 'Microsoft.Compute/virtualMachines')]" \
--output tableIdentify stale assignments (combine with Entra ID sign-in logs):
# Get users without sign-in in last 90 days
az rest --method GET \
--url "https://graph.microsoft.com/v1.0/auditLogs/signIns?\$filter=createdDateTime le $(date -u -d '90 days ago' +%Y-%m-%dT%H:%M:%SZ)"Compliance Reporting
Generate role assignment report:
#!/bin/bash
# role-assignment-report.sh
OUTPUT_FILE="role-assignments-$(date +%Y%m%d).csv"
echo "PrincipalName,PrincipalType,RoleDefinitionName,Scope" > "$OUTPUT_FILE"
az role assignment list --all --query "[].[principalName,principalType,roleDefinitionName,scope]" -o tsv | \
while IFS=$'\t' read -r principal type role scope; do
echo "$principal,$type,$role,$scope" >> "$OUTPUT_FILE"
done
echo "Report generated: $OUTPUT_FILE"Find role assignments without groups:
# List direct user assignments (anti-pattern)
az role assignment list \
--all \
--query "[?principalType=='User'].{User:principalName, Role:roleDefinitionName, Scope:scope}" \
--output tableAudit administrative roles:
# Find all Owner and Contributor assignments
for role in Owner Contributor "User Access Administrator"; do
echo "=== $role ==="
az role assignment list \
--role "$role" \
--all \
--query "[].{Principal:principalName, Scope:scope}" \
--output table
doneAutomated Compliance Checks
Check for policy violations:
#!/bin/bash
# compliance-check.sh
echo "Checking for direct user assignments (should use groups)..."
DIRECT_USERS=$(az role assignment list --all --query "[?principalType=='User'] | length(@)")
if [ "$DIRECT_USERS" -gt 0 ]; then
echo "⚠️ Found $DIRECT_USERS direct user assignments"
else
echo "✓ No direct user assignments"
fi
echo ""
echo "Checking for excessive Owner roles..."
OWNERS=$(az role assignment list --role Owner --all --query "length([])")
if [ "$OWNERS" -gt 5 ]; then
echo "⚠️ Found $OWNERS Owner assignments (expected < 5)"
else
echo "✓ Owner assignments within limits: $OWNERS"
fi
echo ""
echo "Checking for subscription-wide Contributor access..."
SUB_CONTRIBUTORS=$(az role assignment list --role Contributor --query "[?contains(scope, '/subscriptions/') && !contains(scope, '/resourceGroups/')] | length(@)")
if [ "$SUB_CONTRIBUTORS" -gt 0 ]; then
echo "⚠️ Found $SUB_CONTRIBUTORS subscription-wide Contributor assignments"
else
echo "✓ No subscription-wide Contributor assignments"
fiTroubleshooting
Common Issues
Insufficient privileges to assign roles:
Error: The client does not have authorization to perform action
'Microsoft.Authorization/roleAssignments/write'Solution:
- Verify you have Owner or User Access Administrator role at the target scope
- Check:
az role assignment list --assignee $(az ad signed-in-user show --query id -o tsv) --all
Role assignment not taking effect:
- Wait 5-10 minutes for propagation
- Have user re-authenticate:
az logout && az login - Check for deny assignments:
az deny assignment list
Cannot delete role assignment:
Error: Role assignment does not existSolution:
# List to find exact assignment
az role assignment list --assignee jane@contoso.com --all
# Delete by ID instead of parameters
az role assignment delete --ids {assignment-id}Custom role scope issues:
Error: The role definition has invalid assignable scopesSolution:
- Ensure AssignableScopes includes the target subscription
- Cannot be more restrictive than role definition allows
Verification Commands
Check effective permissions:
# What can I do at this scope?
az role assignment list \
--assignee $(az ad signed-in-user show --query id -o tsv) \
--scope /subscriptions/{sub}/resourceGroups/myRG \
--include-inheritedTest specific action:
# Try the action - will show permission error if not allowed
az vm list --resource-group myRGList deny assignments:
az deny assignment list --scope /subscriptions/{subscription-id}Best Practices Summary
1. Use groups, not individual users - Easier management and auditing 2. Principle of least privilege - Grant minimum required permissions 3. Prefer built-in roles - Use custom roles only when necessary 4. Assign at appropriate scope - Resource group level preferred over subscription 5. Regular access reviews - Quarterly review and cleanup 6. Document role purposes - Clear descriptions for custom roles 7. Use managed identities - Avoid service principals when possible 8. Monitor privileged roles - Alert on Owner/Contributor assignments 9. Implement just-in-time (JIT) access - For administrative tasks 10. Version control role definitions - Track changes in Git
Related Documentation
- @user-management.md - User, group, and service principal management
- @resource-management.md - Resource lifecycle and organization
- @cli-patterns.md - Advanced CLI patterns for RBAC operations
- @troubleshooting.md - Additional troubleshooting guidance
- @../examples/role-audit.md - Complete role audit workflow
Azure Troubleshooting Guide
Common issues and solutions for Azure administration tasks.
Table of Contents
1. Authentication and Authorization 2. Resource Operations 3. Networking Issues 4. Deployment Failures 5. CLI and Tooling Problems 6. Performance Issues
Authentication and Authorization
Login Failures
Problem: az login fails or times out
Solutions:
# 1. Use device code flow
az login --use-device-code
# 2. Clear cached credentials
az logout
az account clear
rm -rf ~/.azure/
# 3. Login with specific tenant
az login --tenant {tenant-id}
# 4. Verify system time (authentication uses time-based tokens)
date # Ensure clock is accurate
# 5. Check network/proxy settings
export HTTP_PROXY=http://proxy.company.com:8080
export HTTPS_PROXY=http://proxy.company.com:8080Permission Denied Errors
Problem: "Insufficient privileges" or "Authorization failed"
Diagnosis:
# Check your role assignments
az role assignment list \
--assignee $(az ad signed-in-user show --query id -o tsv) \
--all \
--output table
# Check subscription context
az account show
# Verify resource provider registration
az provider list --query "[?registrationState=='NotRegistered']" --output tableSolutions:
# 1. Register required resource providers
az provider register --namespace Microsoft.Compute
az provider register --namespace Microsoft.Storage
# 2. Verify you're in the correct subscription
az account set --subscription "My Subscription"
# 3. Request appropriate role from subscription administrator
# Minimum roles needed:
# - Reader: View resources
# - Contributor: Create/manage resources
# - Owner: Full access including role assignmentsToken Expiration
Problem: "Authentication token has expired"
Solution:
# Refresh authentication
az login --use-device-code
# For service principal
az login --service-principal \
--username {app-id} \
--password {secret} \
--tenant {tenant-id}Multi-Tenant Issues
Problem: Cannot access resources in different tenants
Solution:
# List available tenants
az account tenant list
# Login with specific tenant
az login --tenant {tenant-id}
# Switch between tenants
az account set --subscription {subscription-in-other-tenant}Resource Operations
Resource Not Found
Problem: "Resource not found" or "ResourceGroupNotFound"
Diagnosis:
# Check if resource exists
az resource show --ids {resource-id}
# List all resources with similar name
az resource list --name {partial-name}
# Check if in correct subscription
az account show
# Search across all subscriptions
az account list --query "[].{Name:name, ID:id}" -o table
for sub in $(az account list --query "[].id" -o tsv); do
echo "Checking subscription: $sub"
az resource list --subscription "$sub" --name {resource-name}
doneResource Locked
Problem: "Cannot delete resource due to lock"
Diagnosis and Solution:
# List locks on resource
az lock list --resource-group myRG
# Show specific lock
az lock show --name LockName --resource-group myRG
# Delete lock (requires appropriate permissions)
az lock delete --name LockName --resource-group myRG
# Delete lock on specific resource
az lock delete \
--name LockName \
--resource-group myRG \
--resource-name myVM \
--resource-type Microsoft.Compute/virtualMachinesQuota Exceeded
Problem: "QuotaExceeded" or "OperationNotAllowed"
Diagnosis:
# Check current quota usage
az vm list-usage --location eastus --output table
# Check specific resource quota
az network vnet list-usage \
--ids /subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Network/virtualNetworks/{vnet}Solution:
- Request quota increase via Azure Portal > Subscription > Usage + quotas
- Or create support ticket for quota increase
- Consider using different VM sizes or regions with available capacity
Resource Provider Not Registered
Problem: "ResourceProviderNotRegistered"
Solution:
# List all resource providers and their registration status
az provider list --query "[].{Namespace:namespace, State:registrationState}" --output table
# Register specific provider
az provider register --namespace Microsoft.Compute
# Check registration status
az provider show --namespace Microsoft.Compute --query registrationState
# Wait for registration to complete (can take a few minutes)
az provider show --namespace Microsoft.Compute --query registrationState -o tsv | \
while read state; do
if [ "$state" == "Registered" ]; then
echo "✓ Provider registered"
break
fi
echo "Waiting for registration... (current state: $state)"
sleep 10
doneNetworking Issues
Cannot Connect to VM
Problem: SSH or RDP connection fails
Diagnosis:
# Check VM is running
az vm show --resource-group myRG --name myVM --query powerState
# Check NSG rules
az network nsg list --resource-group myRG --output table
az network nsg rule list --resource-group myRG --nsg-name myNSG --output table
# Check public IP
az vm show --resource-group myRG --name myVM --show-details --query publicIps -o tsv
# Test connectivity
nc -zv {public-ip} 22 # SSH
nc -zv {public-ip} 3389 # RDPSolutions:
# 1. Verify VM is running
az vm start --resource-group myRG --name myVM
# 2. Add NSG rule for SSH/RDP
az network nsg rule create \
--resource-group myRG \
--nsg-name myNSG \
--name AllowSSH \
--priority 1000 \
--source-address-prefixes '*' \
--destination-port-ranges 22 \
--access Allow \
--protocol Tcp
# 3. Check if public IP exists
az network public-ip list --resource-group myRG --output table
# 4. Use Azure Bastion or Serial Console
az network bastion list --resource-group myRGDNS Resolution Failures
Problem: Cannot resolve Azure DNS names
Diagnosis:
# Test DNS resolution
nslookup myresource.azurewebsites.net
# Check DNS servers
cat /etc/resolv.conf # Linux
ipconfig /all # WindowsSolution:
# Verify private DNS zone configuration
az network private-dns zone list --output table
# Check DNS record sets
az network private-dns record-set list \
--resource-group myRG \
--zone-name myzone.localDeployment Failures
Bicep/ARM Template Errors
Problem: Template deployment fails
Diagnosis:
# View deployment operations
az deployment group show \
--resource-group myRG \
--name myDeployment \
--query properties.error
# List all deployment operations
az deployment operation group list \
--resource-group myRG \
--name myDeployment \
--query "[?properties.provisioningState=='Failed']"
# Get detailed error messages
az deployment operation group list \
--resource-group myRG \
--name myDeployment \
--query "[].{Operation:properties.targetResource.resourceType, Error:properties.statusMessage}" \
--output tableCommon Issues and Solutions:
1. Validation Errors:
# Validate template before deploying
az deployment group validate \
--resource-group myRG \
--template-file main.bicep \
--parameters @parameters.json
# Build Bicep to see compilation errors
az bicep build --file main.bicep2. Resource Name Conflicts:
# Check if resource name already exists
az resource list --name {resource-name}
# Use unique names with deployment name or timestamp
param uniqueSuffix string = uniqueString(resourceGroup().id)
name: 'mystorage${uniqueSuffix}'3. Parameter Type Mismatches:
// Ensure parameters match expected types
{
"parameters": {
"vmSize": {
"value": "Standard_B2s" // String, not number
},
"instanceCount": {
"value": 3 // Number, not string
}
}
}Deployment Timeout
Problem: Deployment times out
Diagnosis:
# Check deployment status
az deployment group show \
--resource-group myRG \
--name myDeployment \
--query properties.provisioningStateSolution:
# Use --no-wait for long-running deployments
az deployment group create \
--resource-group myRG \
--template-file main.bicep \
--no-wait
# Check status later
az deployment group show \
--resource-group myRG \
--name myDeploymentCLI and Tooling Problems
Azure CLI Command Fails
Problem: az command returns errors or unexpected results
Diagnosis:
# Check Azure CLI version
az --version
# Enable debug output
az vm list --debug
# Check for CLI bugs or known issues
az --version # Note version
# Check: https://github.com/Azure/azure-cli/issuesSolutions:
# 1. Update Azure CLI
az upgrade
# 2. Clear CLI cache
rm -rf ~/.azure/
# 3. Reinstall extensions
az extension list
az extension remove --name {extension-name}
az extension add --name {extension-name}
# 4. Verify JSON syntax (common issue)
az vm create --parameters @params.json # Ensure valid JSON
python -m json.tool params.json # Validate JSONJMESPath Query Errors
Problem: --query returns unexpected results or errors
Diagnosis:
# Test query incrementally
az vm list --query "[]" # All items
az vm list --query "[].name" # Just names
az vm list --query "[?location=='eastus']" # With filter
# Use jq for debugging (output to JSON first)
az vm list --output json | jq '.[] | select(.location=="eastus")'Common Issues:
- Incorrect syntax:
[?location=eastus]should be[?location=='eastus'] - Wrong operator:
&&(and) vs||(or) - Nested property access: Use
.notation
Bicep CLI Issues
Problem: az bicep commands fail
Solutions:
# Install/update Bicep
az bicep install
az bicep upgrade
# Verify installation
az bicep version
# Clear Bicep cache
rm -rf ~/.azure/bicep/
# Reinstall manually
az bicep uninstall
az bicep installPerformance Issues
Slow CLI Commands
Problem: Azure CLI commands are slow
Solutions:
# 1. Use specific queries to reduce data transfer
az vm list --query "[].{Name:name, Location:location}" # Faster
az vm list # Returns all data, slower
# 2. Filter at API level
az vm list --resource-group myRG # Faster
az vm list # Queries all resource groups, slower
# 3. Use --output tsv for scripting (faster parsing)
az vm list --query "[].name" -o tsv
# 4. Enable caching
export AZURE_CLI_DISABLE_CONNECTION_VERIFICATION=1 # Use with caution
# 5. Parallel operations with xargs
az vm list --query "[].id" -o tsv | xargs -P 5 -I {} az vm start --ids {}Timeout Errors
Problem: Operations timeout
Solutions:
# Use --no-wait for long operations
az vm create --no-wait
# Increase timeout (if available)
export AZURE_CLI_TIMEOUT=600 # 10 minutes
# Check operation status
az vm show --resource-group myRG --name myVM --query provisioningStateDebugging Workflow
When encountering issues, follow this systematic approach:
1. Identify the Error:
# Capture full error message
az vm create ... 2>&1 | tee error.log2. Enable Debug Mode:
az vm create --debug ... 2>&1 | tee debug.log3. Verify Prerequisites:
- Correct subscription?
- Sufficient permissions?
- Resource providers registered?
- No resource locks?
4. Check Azure Status:
- Azure status: https://status.azure.com
- Service health in Azure Portal
5. Search for Known Issues:
- Azure CLI issues: https://github.com/Azure/azure-cli/issues
- Stack Overflow: https://stackoverflow.com/questions/tagged/azure-cli
- Microsoft Q&A: https://learn.microsoft.com/answers/topics/azure.html
6. Contact Support:
# Create support ticket
az support tickets create \
--ticket-name "MyIssue" \
--title "Brief description" \
--description "Detailed description with error messages" \
--problem-classification "/providers/Microsoft.Support/services/{service}/problemClassifications/{classification}" \
--severity minimalGetting Help
# Command help
az vm --help
az vm create --help
# Search for commands
az find "create vm"
# Interactive mode
az interactive
# Version info
az --version
# Report bug
az feedbackRelated Documentation
- @user-management.md - Identity-related troubleshooting
- @role-assignments.md - RBAC permission issues
- @resource-management.md - Resource operation issues
- @cli-patterns.md - CLI scripting patterns
- @mcp-integration.md - MCP troubleshooting
Complete Environment Setup with azd
Automated deployment of a complete Azure environment for a production web application using Azure Developer CLI (azd) and Bicep.
Scenario
Deploy a production-ready environment for a Node.js web application with:
- Resource group with proper naming and tagging
- Virtual Network with subnets
- App Service Plan and App Service
- Azure SQL Database
- Azure Storage Account
- Application Insights
- Key Vault for secrets
- Proper networking and security configuration
Prerequisites
- Azure CLI and azd installed
- Azure subscription with Contributor access
- Bicep CLI
- Git repository for infrastructure code
Project Structure
myapp-infrastructure/
├── azure.yaml # azd configuration
├── infra/
│ ├── main.bicep # Main infrastructure template
│ ├── modules/
│ │ ├── network.bicep # Virtual network module
│ │ ├── app-service.bicep # App Service module
│ │ ├── database.bicep # SQL Database module
│ │ ├── storage.bicep # Storage Account module
│ │ └── keyvault.bicep # Key Vault module
│ └── parameters/
│ ├── dev.json
│ ├── staging.json
│ └── prod.json
└── src/
└── [application code]Step 1: Initialize azd Project
# Create new directory
mkdir myapp-infrastructure && cd myapp-infrastructure
# Initialize azd project
azd init --template minimal
# Or start from scratch
azd initStep 2: Configure azure.yaml
# azure.yaml
name: myapp
metadata:
template: myapp-infrastructure@0.0.1
services:
web:
project: ./src
language: js
host: appservice
infra:
provider: bicep
path: infra
module: mainStep 3: Create Main Bicep Template
// infra/main.bicep
targetScope = 'subscription'
@minLength(1)
@maxLength(64)
@description('Name of the environment (e.g., dev, staging, prod)')
param environmentName string
@minLength(1)
@description('Primary location for all resources')
param location string
@description('Resource tags')
param tags object = {}
// Generate unique resource names
var abbrs = loadJsonContent('./abbreviations.json')
var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
// Create resource group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
name: '${abbrs.resourcesResourceGroups}${environmentName}-${resourceToken}'
location: location
tags: union(tags, {
Environment: environmentName
ManagedBy: 'azd'
})
}
// Deploy network module
module network 'modules/network.bicep' = {
name: 'network-deployment'
scope: rg
params: {
location: location
environmentName: environmentName
resourceToken: resourceToken
}
}
// Deploy app service module
module appService 'modules/app-service.bicep' = {
name: 'app-service-deployment'
scope: rg
params: {
location: location
environmentName: environmentName
resourceToken: resourceToken
subnetId: network.outputs.appSubnetId
}
}
// Deploy database module
module database 'modules/database.bicep' = {
name: 'database-deployment'
scope: rg
params: {
location: location
environmentName: environmentName
resourceToken: resourceToken
subnetId: network.outputs.dataSubnetId
}
}
// Deploy storage module
module storage 'modules/storage.bicep' = {
name: 'storage-deployment'
scope: rg
params: {
location: location
environmentName: environmentName
resourceToken: resourceToken
}
}
// Deploy Key Vault module
module keyVault 'modules/keyvault.bicep' = {
name: 'keyvault-deployment'
scope: rg
params: {
location: location
environmentName: environmentName
resourceToken: resourceToken
appServicePrincipalId: appService.outputs.identityPrincipalId
}
}
// Outputs
output AZURE_LOCATION string = location
output AZURE_RESOURCE_GROUP string = rg.name
output AZURE_APP_SERVICE_NAME string = appService.outputs.appServiceName
output AZURE_DATABASE_CONNECTION_STRING string = database.outputs.connectionString
output AZURE_STORAGE_ACCOUNT_NAME string = storage.outputs.storageAccountName
output AZURE_KEY_VAULT_NAME string = keyVault.outputs.keyVaultNameStep 4: Create Network Module
// infra/modules/network.bicep
param location string
param environmentName string
param resourceToken string
var vnetName = 'vnet-${environmentName}-${resourceToken}'
var appSubnetName = 'subnet-app'
var dataSubnetName = 'subnet-data'
resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = {
name: vnetName
location: location
properties: {
addressSpace: {
addressPrefixes: ['10.0.0.0/16']
}
subnets: [
{
name: appSubnetName
properties: {
addressPrefix: '10.0.1.0/24'
delegations: [
{
name: 'appservice-delegation'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}
]
serviceEndpoints: [
{
service: 'Microsoft.Storage'
}
{
service: 'Microsoft.Sql'
}
{
service: 'Microsoft.KeyVault'
}
]
}
}
{
name: dataSubnetName
properties: {
addressPrefix: '10.0.2.0/24'
}
}
]
}
}
output vnetId string = vnet.id
output appSubnetId string = vnet.properties.subnets[0].id
output dataSubnetId string = vnet.properties.subnets[1].idStep 5: Deploy with azd
# Login to Azure
azd auth login
# Create new environment
azd env new dev
# Set environment variables
azd env set AZURE_LOCATION eastus
# Provision infrastructure
azd provision
# Deploy application
azd deploy
# Or do both in one command
azd up
# Monitor deployment
azd monitor --overviewStep 6: Multi-Environment Deployment
# Development environment
azd env new development
azd env set AZURE_LOCATION eastus
azd up
# Staging environment
azd env new staging
azd env set AZURE_LOCATION westus
azd up
# Production environment
azd env new production
azd env set AZURE_LOCATION centralus
azd up
# List environments
azd env list
# Switch between environments
azd env select development
azd deploy
azd env select production
azd deployStep 7: CI/CD Integration
# .github/workflows/azure-dev.yml
name: Azure Dev
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
permissions:
id-token: write
contents: read
jobs:
build:
runs-on: ubuntu-latest
env:
AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }}
AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }}
AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
AZURE_ENV_NAME: ${{ secrets.AZURE_ENV_NAME }}
AZURE_LOCATION: ${{ secrets.AZURE_LOCATION }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install azd
uses: Azure/setup-azd@v0.1.0
- name: Log in with Azure (Federated Credentials)
run: |
azd auth login \
--client-id "$AZURE_CLIENT_ID" \
--federated-credential-provider "github" \
--tenant-id "$AZURE_TENANT_ID"
- name: Provision Infrastructure
run: azd provision --no-prompt
- name: Deploy Application
run: azd deploy --no-promptCleanup
# Delete all resources in an environment
azd down
# Delete specific environment
azd env select dev
azd down --purge
# List what will be deleted first
azd down --what-ifAdvanced: Custom Hooks
Create .azd/hooks/preprovision.sh for custom logic before provisioning:
#!/bin/bash
# .azd/hooks/preprovision.sh
set -e
echo "Running pre-provision validation..."
# Check required tools
command -v az >/dev/null 2>&1 || { echo "Azure CLI required"; exit 1; }
command -v bicep >/dev/null 2>&1 || { echo "Bicep CLI required"; exit 1; }
# Validate Bicep templates
echo "Validating Bicep templates..."
az bicep build --file infra/main.bicep
echo "✓ Pre-provision checks passed"Related Documentation
- @docs/resource-management.md - Bicep and ARM templates
- @docs/devops-automation.md - CI/CD pipelines
- @docs/cli-patterns.md - azd CLI patterns
- @../examples/mcp-workflow.md - MCP-powered environment management