Chapter Navigation:
- 📚 Course Home: AZD For Beginners
- 📖 Current Chapter: Chapter 4 - Infrastructure as Code & Deployment
- ⬅️ Previous: Deployment Guide
- ➡️ Next Chapter: Chapter 5: Multi-Agent AI Solutions
- 🔧 Related: Chapter 6: Pre-Deployment Validation
This comprehensive guide covers everything you need to know about provisioning and managing Azure resources using Azure Developer CLI. Learn to implement Infrastructure as Code (IaC) patterns from basic resource creation to advanced enterprise-grade infrastructure architectures using Bicep, ARM templates, Terraform, and Pulumi.
By completing this guide, you will:
- Master Infrastructure as Code principles and Azure resource provisioning
- Understand multiple IaC providers supported by Azure Developer CLI
- Design and implement Bicep templates for common application architectures
- Configure resource parameters, variables, and environment-specific settings
- Implement advanced infrastructure patterns including networking and security
- Manage resource lifecycle, updates, and dependency resolution
Upon completion, you will be able to:
- Design and provision Azure infrastructure using Bicep and ARM templates
- Configure complex multi-service architectures with proper resource dependencies
- Implement parameterized templates for multiple environments and configurations
- Troubleshoot infrastructure provisioning issues and resolve deployment failures
- Apply Azure Well-Architected Framework principles to infrastructure design
- Manage infrastructure updates and implement infrastructure versioning strategies
Azure Developer CLI supports multiple Infrastructure as Code (IaC) providers:
- Bicep (recommended) - Azure's domain-specific language
- ARM Templates - JSON-based Azure Resource Manager templates
- Terraform - Multi-cloud infrastructure tool
- Pulumi - Modern infrastructure as code with programming languages
Azure Account
└── Subscriptions
└── Resource Groups
└── Resources (App Service, Storage, Database, etc.)
- Compute: App Service, Container Apps, Functions, Virtual Machines
- Storage: Storage Account, Cosmos DB, SQL Database, PostgreSQL
- Networking: Virtual Network, Application Gateway, CDN
- Security: Key Vault, Application Insights, Log Analytics
- AI/ML: Cognitive Services, OpenAI, Machine Learning
// infra/main.bicep
@description('The name of the environment')
param environmentName string
@description('The location for all resources')
param location string = resourceGroup().location
@description('The name of the application')
param applicationName string = 'myapp'
// Variables
var resourceToken = toLower(uniqueString(subscription().id, environmentName, location))
var tags = {
'azd-env-name': environmentName
'azd-app': applicationName
}
// Resource Group (created automatically by azd)
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' existing = {
name: '${applicationName}-${environmentName}-rg'
}
// App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2022-03-01' = {
name: '${applicationName}-${environmentName}-plan'
location: location
tags: tags
sku: {
name: 'B1'
capacity: 1
}
properties: {
reserved: true // Linux App Service Plan
}
}
// Web App
resource webApp 'Microsoft.Web/sites@2022-03-01' = {
name: '${applicationName}-web-${resourceToken}'
location: location
tags: tags
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
linuxFxVersion: 'NODE|18-lts'
alwaysOn: true
ftpsState: 'Disabled'
minTlsVersion: '1.2'
appSettings: [
{
name: 'WEBSITES_ENABLE_APP_SERVICE_STORAGE'
value: 'false'
}
{
name: 'NODE_ENV'
value: 'production'
}
]
}
httpsOnly: true
}
}
// Output values for azd
output WEB_URL string = 'https://${webApp.properties.defaultHostName}'
output WEB_NAME string = webApp.name// infra/modules/app-service.bicep
@description('App Service configuration')
param name string
param location string
param planId string
param appSettings array = []
resource webApp 'Microsoft.Web/sites@2022-03-01' = {
name: name
location: location
properties: {
serverFarmId: planId
siteConfig: {
appSettings: appSettings
linuxFxVersion: 'NODE|18-lts'
alwaysOn: true
}
httpsOnly: true
}
}
output hostname string = webApp.properties.defaultHostName
output principalId string = webApp.identity.principalId// infra/main.bicep - Using modules
module webAppModule 'modules/app-service.bicep' = {
name: 'webApp'
params: {
name: '${applicationName}-web-${resourceToken}'
location: location
planId: appServicePlan.id
appSettings: [
{
name: 'API_URL'
value: apiModule.outputs.endpoint
}
{
name: 'DATABASE_URL'
value: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=database-url)'
}
]
}
}@description('Whether to create a database')
param createDatabase bool = true
@description('Database SKU')
param databaseSku string = 'Basic'
resource database 'Microsoft.Sql/servers/databases@2021-11-01' = if (createDatabase) {
name: '${sqlServer.name}/${applicationName}-db'
location: location
sku: {
name: databaseSku
tier: databaseSku == 'Basic' ? 'Basic' : 'Standard'
}
properties: {
collation: 'SQL_Latin1_General_CP1_CI_AS'
}
}resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' = {
name: '${applicationName}-cosmos-${resourceToken}'
location: location
tags: tags
kind: 'GlobalDocumentDB'
properties: {
databaseAccountOfferType: 'Standard'
locations: [
{
locationName: location
failoverPriority: 0
isZoneRedundant: false
}
]
capabilities: [
{
name: 'EnableServerless'
}
]
}
}
resource cosmosDatabase 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases@2023-04-15' = {
parent: cosmosAccount
name: '${applicationName}db'
properties: {
resource: {
id: '${applicationName}db'
}
}
}
resource todoContainer 'Microsoft.DocumentDB/databaseAccounts/sqlDatabases/containers@2023-04-15' = {
parent: cosmosDatabase
name: 'todos'
properties: {
resource: {
id: 'todos'
partitionKey: {
paths: ['/userId']
kind: 'Hash'
}
}
}
}resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = {
name: '${applicationName}-postgres-${resourceToken}'
location: location
tags: tags
sku: {
name: 'Standard_B1ms'
tier: 'Burstable'
}
properties: {
version: '14'
administratorLogin: 'dbadmin'
administratorLoginPassword: databasePassword
storage: {
storageSizeGB: 32
}
backup: {
backupRetentionDays: 7
geoRedundantBackup: 'Disabled'
}
highAvailability: {
mode: 'Disabled'
}
}
}
resource postgresDatabase 'Microsoft.DBforPostgreSQL/flexibleServers/databases@2022-12-01' = {
parent: postgresServer
name: '${applicationName}db'
properties: {
charset: 'utf8'
collation: 'en_US.utf8'
}
}
// Allow Azure services to connect
resource firewallRule 'Microsoft.DBforPostgreSQL/flexibleServers/firewallRules@2022-12-01' = {
parent: postgresServer
name: 'AllowAzureServices'
properties: {
startIpAddress: '0.0.0.0'
endIpAddress: '0.0.0.0'
}
}resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' = {
name: '${applicationName}-kv-${resourceToken}'
location: location
tags: tags
properties: {
sku: {
family: 'A'
name: 'standard'
}
tenantId: subscription().tenantId
enableRbacAuthorization: true
enableSoftDelete: true
softDeleteRetentionInDays: 7
}
}
// Grant Key Vault access to the web app
resource webAppKeyVaultAccess 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(keyVault.id, webApp.id, 'Key Vault Secrets User')
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'4633458b-17de-408a-b874-0445c86b69e6' // Key Vault Secrets User
)
principalId: webApp.identity.principalId
principalType: 'ServicePrincipal'
}
}
// Store database connection string in Key Vault
resource databaseConnectionSecret 'Microsoft.KeyVault/vaults/secrets@2023-02-01' = {
parent: keyVault
name: 'database-connection-string'
properties: {
value: 'Server=${postgresServer.properties.fullyQualifiedDomainName};Database=${postgresDatabase.name};Port=5432;User Id=${postgresServer.properties.administratorLogin};Password=${databasePassword};'
}
}resource webApp 'Microsoft.Web/sites@2022-03-01' = {
name: '${applicationName}-web-${resourceToken}'
location: location
identity: {
type: 'SystemAssigned'
}
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
appSettings: [
{
name: 'DATABASE_CONNECTION_STRING'
value: '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=database-connection-string)'
}
{
name: 'AZURE_CLIENT_ID'
value: webApp.identity.principalId
}
]
}
}
}resource vnet 'Microsoft.Network/virtualNetworks@2023-04-01' = {
name: '${applicationName}-vnet-${resourceToken}'
location: location
tags: tags
properties: {
addressSpace: {
addressPrefixes: ['10.0.0.0/16']
}
subnets: [
{
name: 'app-subnet'
properties: {
addressPrefix: '10.0.1.0/24'
serviceEndpoints: [
{
service: 'Microsoft.Storage'
}
{
service: 'Microsoft.KeyVault'
}
]
}
}
{
name: 'db-subnet'
properties: {
addressPrefix: '10.0.2.0/24'
delegations: [
{
name: 'postgres-delegation'
properties: {
serviceName: 'Microsoft.DBforPostgreSQL/flexibleServers'
}
}
]
}
}
]
}
}
// Private DNS Zone for PostgreSQL
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: '${applicationName}.postgres.database.azure.com'
location: 'global'
tags: tags
}
resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: privateDnsZone
name: '${applicationName}-dns-link'
location: 'global'
properties: {
registrationEnabled: false
virtualNetwork: {
id: vnet.id
}
}
}resource publicIP 'Microsoft.Network/publicIPAddresses@2023-04-01' = {
name: '${applicationName}-agw-pip-${resourceToken}'
location: location
tags: tags
sku: {
name: 'Standard'
tier: 'Regional'
}
properties: {
publicIPAllocationMethod: 'Static'
}
}
resource applicationGateway 'Microsoft.Network/applicationGateways@2023-04-01' = {
name: '${applicationName}-agw-${resourceToken}'
location: location
tags: tags
properties: {
sku: {
name: 'Standard_v2'
tier: 'Standard_v2'
capacity: 1
}
gatewayIPConfigurations: [
{
name: 'appGatewayIpConfig'
properties: {
subnet: {
id: '${vnet.id}/subnets/gateway-subnet'
}
}
}
]
frontendIPConfigurations: [
{
name: 'appGatewayFrontendIP'
properties: {
publicIPAddress: {
id: publicIP.id
}
}
}
]
frontendPorts: [
{
name: 'port80'
properties: {
port: 80
}
}
{
name: 'port443'
properties: {
port: 443
}
}
]
}
}resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: '${applicationName}-logs-${resourceToken}'
location: location
tags: tags
properties: {
sku: {
name: 'PerGB2018'
}
retentionInDays: 30
}
}
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = {
name: '${applicationName}-ai-${resourceToken}'
location: location
tags: tags
kind: 'web'
properties: {
Application_Type: 'web'
WorkspaceResourceId: logAnalytics.id
}
}
// Output connection string for applications
output APPLICATION_INSIGHTS_CONNECTION_STRING string = applicationInsights.properties.ConnectionStringresource cpuAlert 'Microsoft.Insights/metricAlerts@2018-03-01' = {
name: '${applicationName}-cpu-alert'
location: 'global'
tags: tags
properties: {
description: 'Alert when CPU usage is high'
severity: 2
enabled: true
scopes: [webApp.id]
evaluationFrequency: 'PT5M'
windowSize: 'PT5M'
criteria: {
'odata.type': 'Microsoft.Azure.Monitor.SingleResourceMultipleMetricCriteria'
allOf: [
{
name: 'CPU Usage'
metricName: 'CpuPercentage'
operator: 'GreaterThan'
threshold: 80
timeAggregation: 'Average'
}
]
}
actions: [
{
actionGroupId: actionGroup.id
}
]
}
}// infra/main.parameters.dev.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environmentName": {
"value": "${AZURE_ENV_NAME}"
},
"location": {
"value": "${AZURE_LOCATION}"
},
"appServiceSku": {
"value": "B1"
},
"databaseSku": {
"value": "Standard_B1ms"
},
"enableBackup": {
"value": false
}
}
}// infra/main.parameters.prod.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"environmentName": {
"value": "${AZURE_ENV_NAME}"
},
"location": {
"value": "${AZURE_LOCATION}"
},
"appServiceSku": {
"value": "P1v3"
},
"databaseSku": {
"value": "Standard_D2s_v3"
},
"enableBackup": {
"value": true
},
"replicaCount": {
"value": 3
}
}
}@description('Environment type (dev, staging, prod)')
@allowed(['dev', 'staging', 'prod'])
param environmentType string = 'dev'
// Development resources
resource devStorage 'Microsoft.Storage/storageAccounts@2023-01-01' = if (environmentType == 'dev') {
name: '${applicationName}devstorage${resourceToken}'
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
// Production resources with geo-redundancy
resource prodStorage 'Microsoft.Storage/storageAccounts@2023-01-01' = if (environmentType == 'prod') {
name: '${applicationName}prodstorage${resourceToken}'
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_GRS'
}
properties: {
accessTier: 'Hot'
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
}
}@description('Primary region')
param primaryLocation string = 'eastus2'
@description('Secondary region')
param secondaryLocation string = 'westus2'
// Primary region resources
module primaryRegion 'modules/region.bicep' = {
name: 'primary-region'
params: {
location: primaryLocation
isPrimary: true
applicationName: applicationName
environmentName: environmentName
}
}
// Secondary region resources
module secondaryRegion 'modules/region.bicep' = {
name: 'secondary-region'
params: {
location: secondaryLocation
isPrimary: false
applicationName: applicationName
environmentName: environmentName
}
}
// Traffic Manager for global load balancing
resource trafficManager 'Microsoft.Network/trafficmanagerprofiles@2022-04-01' = {
name: '${applicationName}-tm-${resourceToken}'
location: 'global'
properties: {
profileStatus: 'Enabled'
trafficRoutingMethod: 'Priority'
dnsConfig: {
relativeName: '${applicationName}-${environmentName}'
ttl: 30
}
monitorConfig: {
protocol: 'HTTPS'
port: 443
path: '/health'
}
endpoints: [
{
name: 'primary-endpoint'
type: 'Microsoft.Network/trafficManagerProfiles/azureEndpoints'
properties: {
targetResourceId: primaryRegion.outputs.webAppId
priority: 1
}
}
{
name: 'secondary-endpoint'
type: 'Microsoft.Network/trafficManagerProfiles/azureEndpoints'
properties: {
targetResourceId: secondaryRegion.outputs.webAppId
priority: 2
}
}
]
}
}// infra/test/main.test.bicep
param location string = resourceGroup().location
module mainTemplate '../main.bicep' = {
name: 'main-template-test'
params: {
environmentName: 'test'
location: location
applicationName: 'testapp'
}
}
// Test assertions
resource testScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'test-deployment'
location: location
kind: 'AzurePowerShell'
properties: {
azPowerShellVersion: '8.0'
scriptContent: '''
$webAppName = "${mainTemplate.outputs.WEB_NAME}"
$response = Invoke-WebRequest -Uri "https://${mainTemplate.outputs.WEB_URL}/health" -UseBasicParsing
if ($response.StatusCode -ne 200) {
throw "Health check failed"
}
Write-Output "All tests passed!"
'''
timeout: 'PT10M'
cleanupPreference: 'OnSuccess'
retentionInterval: 'P1D'
}
}The azd provision --preview feature lets you simulate infrastructure provisioning before actually deploying resources. It's similar in spirit to terraform plan or bicep what-if, giving you a dry-run view of what changes would be made to your Azure environment.
- Analyzes your IaC templates (Bicep or Terraform)
- Shows a preview of resource changes: additions, deletions, updates
- Does not apply changes — it's read-only and safe to run
# Preview infrastructure changes before deployment
azd provision --preview
# Preview for specific environment
azd provision --preview -e productionThis command helps you:
- Validate infrastructure changes before committing resources
- Catch misconfigurations early in development cycle
- Collaborate safely in team environments
- Ensure least-privilege deployments without surprises
It's especially useful when:
- Working with complex multi-service environments
- Making changes to production infrastructure
- Validating template modifications before PR approval
- Training new team members on infrastructure patterns
Exact preview output varies by provider and project structure, but the result should clearly identify proposed changes before anything is applied.
$ azd provision --preview
🔍 Previewing infrastructure changes...
The following resources will be created:
+ azurerm_resource_group.rg
+ azurerm_app_service_plan.plan
+ azurerm_linux_web_app.web
+ azurerm_cosmosdb_account.cosmos
The following resources will be modified:
~ azurerm_key_vault.kv
~ access_policy (forces replacement)
The following resources will be destroyed:
- azurerm_storage_account.old_storage
⚠️ Warning: 1 resource will be replaced
✅ Preview completed successfully!# Preview infrastructure changes first (RECOMMENDED)
azd provision --preview
# Apply changes after preview confirmation
azd provision --confirm-with-no-prompt
# For rollback, use Git to revert infrastructure changes:
git revert HEAD # Revert last infrastructure commit
azd provision # Apply previous infrastructure stateresource migrationScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = {
name: 'database-migration'
location: location
kind: 'AzureCLI'
properties: {
azCliVersion: '2.40.0'
scriptContent: '''
# Install database migration tools
npm install -g db-migrate db-migrate-pg
# Run migrations
db-migrate up --config database.json --env production
echo "Database migration completed successfully"
'''
environmentVariables: [
{
name: 'DATABASE_URL'
secureValue: databaseConnectionString
}
]
timeout: 'PT30M'
cleanupPreference: 'OnSuccess'
}
}var naming = {
resourceGroup: 'rg-${applicationName}-${environmentName}-${location}'
appService: '${applicationName}-web-${resourceToken}'
database: '${applicationName}-db-${resourceToken}'
storage: '${take(replace(applicationName, '-', ''), 15)}${environmentName}sa${take(resourceToken, 8)}'
keyVault: '${take(applicationName, 15)}-kv-${take(resourceToken, 8)}'
}var commonTags = {
'azd-env-name': environmentName
'azd-app': applicationName
'environment': environmentName
'cost-center': 'engineering'
'owner': 'platform-team'
'project': applicationName
'created-date': utcNow('yyyy-MM-dd')
}@description('Environment name')
@minLength(3)
@maxLength(20)
param environmentName string
@description('Location for resources')
@allowed(['eastus2', 'westus2', 'centralus'])
param location string
@description('App Service SKU')
@allowed(['B1', 'B2', 'S1', 'S2', 'P1v3', 'P2v3'])
param appServiceSku string = 'B1'// Service endpoints
output WEB_URL string = 'https://${webApp.properties.defaultHostName}'
output API_URL string = 'https://${apiApp.properties.defaultHostName}'
// Resource identifiers
output WEB_APP_NAME string = webApp.name
output API_APP_NAME string = apiApp.name
output DATABASE_NAME string = database.name
// Connection strings (for secure reference)
output DATABASE_CONNECTION_STRING_KEY string = '@Microsoft.KeyVault(VaultName=${keyVault.name};SecretName=database-connection-string)'- Pre-deployment Planning - Validate resource availability
- Common Issues - Troubleshoot infrastructure problems
- Debugging Guide - Debug provisioning issues
- SKU Selection - Choose appropriate service tiers
- Azure Bicep Documentation
- Azure Resource Manager Templates
- Azure Architecture Center
- Azure Well-Architected Framework
Navigation
- Previous Lesson: Deployment Guide
- Next Lesson: Capacity Planning