
Building on our storage account foundation from Part 1, we’ll now create a complete web application infrastructure. This part covers virtual networks, app services, SQL databases, and how to properly manage dependencies between resources.
Welcome to Part 2 of our Infrastructure as Code series. Today we’re diving deep into production-ready ARM templates that can handle real-world enterprise scenarios.
What You’ll Build
- Virtual Network with multiple subnets
- App Service with custom domain support
- Azure SQL Database with security configurations
- Application Insights for monitoring
- Proper resource dependencies and ordering
Architecture Overview
Our template will deploy this architecture:
Resource Group
├── Virtual Network
│ ├── Web Subnet
│ ├── Database Subnet
│ └── Network Security Groups
├── App Service Plan
├── App Service (Web App)
├── SQL Server
├── SQL Database
├── Application Insights
└── Storage Account
Production-Ready Template Structure
Here’s our comprehensive ARM template with advanced features:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"type": "string",
"metadata": {
"description": "Base name for all resources"
},
"minLength": 3,
"maxLength": 10
},
"environment": {
"type": "string",
"allowedValues": ["dev", "staging", "prod"],
"metadata": {
"description": "Environment designation"
}
},
"sqlAdministratorLogin": {
"type": "string",
"metadata": {
"description": "SQL Server administrator login"
}
},
"sqlAdministratorPassword": {
"type": "securestring",
"metadata": {
"description": "SQL Server administrator password"
}
},
"appServicePlanSku": {
"type": "object",
"defaultValue": {
"name": "S1",
"capacity": 1
}
}
},
"variables": {
"naming": {
"prefix": "[concat(parameters('projectName'), '-', parameters('environment'))]",
"vnet": "[concat('vnet-', variables('naming').prefix)]",
"appServicePlan": "[concat('asp-', variables('naming').prefix)]",
"appService": "[concat('app-', variables('naming').prefix, '-', uniqueString(resourceGroup().id))]",
"sqlServer": "[concat('sql-', variables('naming').prefix, '-', uniqueString(resourceGroup().id))]"
},
"commonTags": {
"Environment": "[parameters('environment')]",
"Project": "[parameters('projectName')]",
"CreatedBy": "ARM-Template"
}
}
}
Network Security Implementation
Implementing proper network security with Network Security Groups:
{
"type": "Microsoft.Network/networkSecurityGroups",
"apiVersion": "2023-04-01",
"name": "[concat('nsg-web-', variables('naming').prefix)]",
"location": "[resourceGroup().location]",
"tags": "[variables('commonTags')]",
"properties": {
"securityRules": [
{
"name": "AllowHTTPS",
"properties": {
"protocol": "Tcp",
"sourcePortRange": "*",
"destinationPortRange": "443",
"sourceAddressPrefix": "*",
"destinationAddressPrefix": "*",
"access": "Allow",
"priority": 100,
"direction": "Inbound"
}
},
{
"name": "AllowHTTP",
"properties": {
"protocol": "Tcp",
"sourcePortRange": "*",
"destinationPortRange": "80",
"sourceAddressPrefix": "*",
"destinationAddressPrefix": "*",
"access": "Allow",
"priority": 110,
"direction": "Inbound"
}
}
]
}
}
SQL Server with Security Best Practices
{
"type": "Microsoft.Sql/servers",
"apiVersion": "2023-05-01-preview",
"name": "[variables('naming').sqlServer]",
"location": "[resourceGroup().location]",
"tags": "[variables('commonTags')]",
"properties": {
"administratorLogin": "[parameters('sqlAdministratorLogin')]",
"administratorLoginPassword": "[parameters('sqlAdministratorPassword')]",
"version": "12.0",
"publicNetworkAccess": "Disabled"
}
}
App Service with Monitoring
{
"type": "Microsoft.Web/sites",
"apiVersion": "2023-01-01",
"name": "[variables('naming').appService]",
"location": "[resourceGroup().location]",
"dependsOn": [
"[resourceId('Microsoft.Web/serverfarms', variables('naming').appServicePlan)]",
"[resourceId('Microsoft.Insights/components', variables('naming').appInsights)]"
],
"properties": {
"serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('naming').appServicePlan)]",
"siteConfig": {
"appSettings": [
{
"name": "APPINSIGHTS_INSTRUMENTATIONKEY",
"value": "[reference(resourceId('Microsoft.Insights/components', variables('naming').appInsights)).InstrumentationKey]"
},
{
"name": "Environment",
"value": "[parameters('environment')]"
}
]
},
"httpsOnly": true
}
}

Environment-Specific Parameters
Development Environment (dev.parameters.json):
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"value": "webapp"
},
"environment": {
"value": "dev"
},
"sqlAdministratorLogin": {
"value": "sqladmin"
},
"sqlAdministratorPassword": {
"value": "YourSecureP@ssw0rd!"
},
"appServicePlanSku": {
"value": {
"name": "F1",
"capacity": 1
}
}
}
}
Production Environment (prod.parameters.json):
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"projectName": {
"value": "webapp"
},
"environment": {
"value": "prod"
},
"sqlAdministratorLogin": {
"value": "sqladmin"
},
"sqlAdministratorPassword": {
"reference": {
"keyVault": {
"id": "/subscriptions/{subscription-id}/resourceGroups/rg-shared/providers/Microsoft.KeyVault/vaults/kv-shared"
},
"secretName": "SqlAdminPassword"
}
},
"appServicePlanSku": {
"value": {
"name": "P1V2",
"capacity": 2
}
}
}
}
Deployment Commands
# Deploy to development
az deployment group create \
--resource-group rg-webapp-dev \
--template-file webapp-infrastructure.json \
--parameters @dev.parameters.json
# Deploy to production
az deployment group create \
--resource-group rg-webapp-prod \
--template-file webapp-infrastructure.json \
--parameters @prod.parameters.json
# Validate template before deployment
az deployment group validate \
--resource-group rg-webapp-dev \
--template-file webapp-infrastructure.json \
--parameters @dev.parameters.json
Advanced Concepts Covered
1. Resource Dependencies
- Explicit dependencies using
dependsOn
- Implicit dependencies through resource references
- Proper ordering for network security and VNet integration
2. Naming Conventions
- Structured naming using variables
- Unique string generation for globally unique resources
- Environment-specific prefixes
3. Security Best Practices
- Network Security Groups with least privilege
- SQL Server with disabled public access
- HTTPS-only configuration
- Storage account security settings
4. Configuration Management
- App Settings and Connection Strings
- Application Insights integration
- Environment-specific parameters
Testing Your Deployment
- Connectivity Test: Verify app can connect to database
- Security Test: Confirm SQL Server is not publicly accessible
- Monitoring Test: Check Application Insights is receiving telemetry
- Performance Test: Validate App Service Plan scaling
What’s Next?
In Part 3, we’ll explore advanced ARM template patterns including nested templates, conditional deployments, and copy loops for creating multiple similar resources efficiently.
We’ll cover:
- Nested and linked template patterns
- Conditional resource deployment
- Copy loops for arrays and objects
- Template modularity and reusability
- Advanced parameter handling
This is Part 2 of our 7-part series on Infrastructure as Code with ARM Templates and Bicep. We’re building production-ready infrastructure templates step by step.