This repository contains infrastructure as code (Bicep) to deploy a comprehensive environment for Azure Administrator (AZ-104) training and demonstrations.
-
Verify VM SKU availability in your target regions: This deployment uses
Standard_B2s_v2VM SKUs by default. Check that this SKU is available in your chosen regions:az vm list-skus --resource-type virtualMachines --query "[?name=='Standard_B2s_v2'].locations[]" -o tsvIf the SKU is not available in your target regions, you can modify the VM sizes in
infra/main.parameters.jsonor choose different regions. -
Check App Service quota in your target regions: Verify that your subscription has sufficient App Service quota for the deployment. Check that both "Total Regional VMs" and "S1 VMs" quotas are not set to 0 in your target regions.
-
Configure deployment parameters (optional): Before deploying, you can customize the deployment by editing
infra/main.parameters.json. See the "Configuration" section below for details. -
Verify subscription access:
- Ensure you have Owner or Contributor access to the subscription
- For governance components, you need User Access Administrator to create custom roles
- You need Azure Developer CLI - AZD, in case that's your first azd deployment go ahead and install it.
- When installing AZD, the following tools will be installed on your machine as well, if not already installed:
- Create a new folder on your machine and navigate to it.
mkdir -p sqltattoo/azd-az104-all-in-one && cd sqltattoo/azd-az104-all-in-one
- Next, run
azd initto initialize the deployment.
azd init -t sqltattoo/azd-az104-all-in-one
- Last, run
azd upto trigger an actual deployment.
azd up
During azd up you will be prompted for:
| Prompt | Purpose | Example |
|---|---|---|
hubLocation |
Primary region β used for hub, spoke1, and workload VNets | uksouth |
spoke2Location |
Cross-region spoke β must be different from hub to demonstrate multi-region routing | northeurope |
adminPassword |
VM administrator password | (secure input) |
The separate
spoke2Locationprompt is intentional β deploying spoke2 in a different region is a core AZ-104 demo scenario (cross-region VNet peering, latency-based routing).
Optional: If you need to force a redeployment or explicitly pass the parameter file:
azd up --force # Force redeployment even if no changes detected
# Or explicitly reference the parameter file:
azd deploy --parameters @infra/main.parameters.jsonCheck the demo guide for details on the demo scenario.
To remove all deployed resources:
# Standard cleanup (prompts for confirmation)
azd down
# Skip confirmation prompts
azd down --force
# Also purge soft-deleted resources (Key Vault, etc.)
azd down --purge --forceNote: The azd down command automatically cleans up subscription-scoped governance deployments via a postdown hook. The governance deployment (custom RBAC roles and policy definitions) is removed automatically.
If you need to manually clean up the governance deployment:
# Check the hub location from your parameter file
$hubLocation = (Get-Content infra/main.parameters.json | ConvertFrom-Json).parameters.hubLocation.value
# Delete the subscription-scoped governance deployment
az deployment sub delete --name "governance-$hubLocation"
# Or run the cleanup hook manually
azd hooks run postdownUpdated: November 17, 2025
All deployment configuration is managed through the Bicep parameters file infra/main.parameters.json. This provides predictable, consistent behavior across all deployments.
Why we moved from azure.yaml to a parameter file:
- The
infra.parameterssection inazure.yamldid not reliably control runtime deployments - Changes to feature toggles (like
deployBastion,deployVpnGateway) and VM sizes were not consistently applied - A standard ARM/Bicep parameter file ensures Azure Resource Manager processes parameters correctly every time
Edit infra/main.parameters.json before running azd up. Key parameters you can customize:
Locations:
hubLocation, spoke1Location, and workloadLocation are bound to AZURE_LOCATION (the primary region you choose during azd up). spoke2Location is prompted separately β set it to a different region to demonstrate cross-region routing scenarios.
Feature Toggles:
"deployBastion": { "value": false }, // Set to false to skip Bastion (saves cost/quota)
"deployVpnGateway": { "value": false }, // Set to false to skip VPN Gateway
"deployKeyVault": { "value": true } // Enable/disable Key Vault demosVM Size Configuration:
Control VM sizes for cost optimization or quota constraints:
"defaultVmSize": { "value": "Standard_B2s_v2" }, // Default for all tiers
"webTierVmSize": { "value": "" }, // Empty = use default
"appTierVmSize": { "value": "Standard_B2s_v2" }, // Override for app tier only
"workloadTierVmSize": { "value": "" }, // Empty = use default
"vmssVmSize": { "value": "Standard_B2s_v2" } // VMSS instance sizeExample: Low-quota environment
"defaultVmSize": { "value": "Standard_B1s" },
"deployBastion": { "value": false },
"deployVpnGateway": { "value": false }Naming and DNS:
"publicDnsZoneBase": { "value": "contoso.com" },
"privateDnsZoneBase": { "value": "contoso.local" },
"vaultName": { "value": "contoso-rsv" },
"storageAccountPrefix": { "value": "staz104" },
"adminObjectId": { "value": "" } // Optional: your Azure AD Object ID for Key Vault accessAfter editing the parameter file, run:
azd upNote: adminUsername and adminPassword are handled interactively by azd during deployment and should not be added to the parameter file.
Important Note (Updated: October 17, 2025)
If you deployed this solution before October 17, 2025, you may encounter deployment errors when redeploying to a different Azure region. This is due to a static subscription-scoped governance deployment that was location-bound.
You'll see an error like:
InvalidDeploymentLocation: The deployment 'governance-components' already exists in location 'uksouth'.
Before running azd up in a new region, delete the old subscription-scoped deployment:
# Delete the static governance deployment (one-time cleanup)
az deployment sub delete --name governance-components
# Optional: List and clean up custom roles if starting fresh
az role definition list --custom-role-only true --query "[?contains(roleName, 'AZ104')]"
# Optional: List policy assignments for review
az policy assignment list --query "[?contains(displayName, 'AZ104')]"After cleanup, proceed with normal deployment:
azd upWhat changed: The governance deployment now uses a dynamic name based on your hub location (e.g., governance-westeurope), allowing multi-region deployments without conflicts.
- Key Vault deployment fails: Verify your Object ID is correct and that you have sufficient permissions
- Custom RBAC role not visible: It may take a few minutes for the role to appear in the Azure Portal
- Monitoring agent failures: Ensure VMs are fully provisioned before deploying monitoring
- Location conflicts: See the "Cleanup for Previous Deployments" section above
