
Infrastructure as Code (IaC) has revolutionized how we deploy and manage cloud resources. Instead of clicking through Azure portal screens, we can define our entire infrastructure in declarative templates that are version-controlled, repeatable, and auditable.
This is the first part of our comprehensive 7-part series on Infrastructure as Code using Azure Resource Manager (ARM) templates and Bicep. In this series, we’ll take you from basic concepts to enterprise-scale deployment patterns.
What You’ll Learn in This Series
- Part 1: ARM template anatomy and core concepts
- Part 2: Building production-ready infrastructure
- Part 3: Advanced ARM template patterns
- Part 4: Introduction to Bicep – Modern Infrastructure as Code
- Part 5: Advanced Bicep patterns and enterprise modules
- Part 6: CI/CD integration and deployment strategies
- Part 7: Enterprise governance and compliance automation
ARM Template Fundamentals
Azure Resource Manager (ARM) templates are Azure’s native Infrastructure as Code solution, using JSON to describe what resources you want and how they should be configured.
ARM Template Structure
Every ARM template follows this basic structure:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {},
"variables": {},
"resources": [],
"outputs": {}
}
Your First ARM Template: Storage Account
Let’s create a template that deploys a storage account with configurable settings:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"metadata": {
"description": "Name of the storage account"
},
"minLength": 3,
"maxLength": 24
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "Location for the storage account"
}
},
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_RAGRS",
"Premium_LRS"
],
"metadata": {
"description": "Storage account replication type"
}
},
"environment": {
"type": "string",
"allowedValues": [
"dev",
"staging",
"prod"
],
"metadata": {
"description": "Environment designation"
}
}
},
"variables": {
"storageAccountNameFormatted": "[toLower(parameters('storageAccountName'))]",
"commonTags": {
"Environment": "[parameters('environment')]",
"Project": "IaC-Demo",
"CreatedBy": "ARM-Template"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2023-01-01",
"name": "[variables('storageAccountNameFormatted')]",
"location": "[parameters('location')]",
"tags": "[variables('commonTags')]",
"sku": {
"name": "[parameters('storageAccountType')]"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot",
"allowBlobPublicAccess": false,
"allowSharedKeyAccess": true,
"encryption": {
"services": {
"blob": {
"enabled": true
},
"file": {
"enabled": true
}
},
"keySource": "Microsoft.Storage"
},
"networkAcls": {
"defaultAction": "Allow"
}
}
}
],
"outputs": {
"storageAccountId": {
"type": "string",
"value": "[resourceId('Microsoft.Storage/storageAccounts', variables('storageAccountNameFormatted'))]"
},
"storageAccountName": {
"type": "string",
"value": "[variables('storageAccountNameFormatted')]"
},
"primaryEndpoints": {
"type": "object",
"value": "[reference(variables('storageAccountNameFormatted')).primaryEndpoints]"
}
}
}
Parameter File Example
Create a separate parameters file for environment-specific values:
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"value": "mystoragedev001"
},
"storageAccountType": {
"value": "Standard_LRS"
},
"environment": {
"value": "dev"
}
}
}
Deployment Commands
Using Azure CLI:
# Create resource group
az group create --name rg-iac-demo --location eastus
# Deploy template
az deployment group create \
--resource-group rg-iac-demo \
--template-file storage-account.json \
--parameters @storage-account.parameters.json
Using PowerShell:
# Create resource group
New-AzResourceGroup -Name "rg-iac-demo" -Location "East US"
# Deploy template
New-AzResourceGroupDeployment `
-ResourceGroupName "rg-iac-demo" `
-TemplateFile "storage-account.json" `
-TemplateParameterFile "storage-account.parameters.json"

Key Concepts Explained
- Parameters: Input values that make templates reusable across environments
- Variables: Calculated values that simplify complex expressions
- Functions: Built-in functions like
resourceGroup()
,toLower()
,reference()
- Outputs: Return values from deployment for use in other templates or scripts
Best Practices Introduced
- Naming Conventions: Use consistent, descriptive names
- Parameter Validation: Use
minLength
,maxLength
, andallowedValues
- Default Values: Provide sensible defaults where appropriate
- Tags: Implement consistent tagging strategy
- Security: Disable public blob access by default
What’s Next?
In Part 2, we’ll expand this foundation to create a complete web application infrastructure including virtual networks, app services, and databases, while exploring resource dependencies and advanced ARM template features.
We’ll build a production-ready template that includes:
- 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
Stay tuned for the next part where we’ll dive deeper into building enterprise-ready infrastructure templates!
This is Part 1 of our 7-part series on Infrastructure as Code with ARM Templates and Bicep. Follow along as we build from basic concepts to enterprise-scale deployment patterns.