- Infrastructure as Code with ARM Templates and Bicep: Part 1 – ARM Template Fundamentals
- Infrastructure as Code with ARM Templates and Bicep: Part 2 – Building Production-Ready Infrastructure
- Infrastructure as Code with ARM Templates and Bicep: Part 3 – Advanced ARM Template Patterns
- Infrastructure as Code with ARM Templates and Bicep: Part 4 – Introduction to Bicep
- Infrastructure as Code with ARM Templates and Bicep: Part 5 – Advanced Bicep Patterns
- Infrastructure as Code with ARM Templates and Bicep: Part 6 – CI/CD Integration and Advanced Deployment
- Infrastructure as Code with ARM Templates and Bicep: Part 7 – Enterprise Governance and Compliance

Azure Bicep is a domain-specific language (DSL) that provides a more concise and readable syntax for deploying Azure resources. Bicep compiles to ARM templates, so you get all the benefits of ARM with a much better authoring experience.
Welcome to Part 4 of our Infrastructure as Code series! Today we’re making the transition from ARM templates to Bicep and discovering why it’s the future of Azure Infrastructure as Code.
Why Bicep?
ARM Template Challenges: Verbose JSON syntax, complex expressions, difficult to read and maintain, limited IntelliSense support.
Bicep Advantages: Cleaner syntax, strong typing and IntelliSense, better error messages, modular design, day-0 support for new Azure features.
Setting Up Your Bicep Environment
# Install via Azure CLI
az bicep install
# Update to latest version
az bicep upgrade
# Install VS Code extension
code --install-extension ms-azuretools.vscode-bicepBasic Bicep Syntax
// Parameters
@description('Name of the storage account')
@minLength(3)
@maxLength(24)
param storageAccountName string
@description('Environment designation')
@allowed([
'dev'
'staging'
'prod'
])
param environment string
// Variables
var storageAccountNameFormatted = toLower(storageAccountName)
var commonTags = {
Environment: environment
Project: 'IaC-Demo'
CreatedBy: 'Bicep-Template'
}
// Resources
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageAccountNameFormatted
location: resourceGroup().location
tags: commonTags
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
allowBlobPublicAccess: false
supportsHttpsTrafficOnly: true
}
}
// Outputs
output storageAccountId string = storageAccount.id
output storageAccountName string = storageAccount.nameConverting Production Infrastructure to Bicep
// Parameters
@description('Base name for all resources')
param projectName string
@description('Environment designation')
@allowed(['dev', 'staging', 'prod'])
param environment string
@description('SQL administrator credentials')
param sqlAdministratorLogin string
@secure()
param sqlAdministratorPassword string
// Variables
var naming = {
appService: 'app-${projectName}-${environment}-${uniqueString(resourceGroup().id)}'
sqlServer: 'sql-${projectName}-${environment}-${uniqueString(resourceGroup().id)}'
appInsights: 'appi-${projectName}-${environment}'
appServicePlan: 'asp-${projectName}-${environment}'
}
// Application Insights
resource appInsights 'Microsoft.Insights/components@2020-02-02' = {
name: naming.appInsights
location: resourceGroup().location
kind: 'web'
properties: {
Application_Type: 'web'
RetentionInDays: environment == 'prod' ? 90 : 30
}
}
// App Service Plan
resource appServicePlan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: naming.appServicePlan
location: resourceGroup().location
sku: {
name: environment == 'prod' ? 'P1V2' : 'F1'
capacity: environment == 'prod' ? 2 : 1
}
}
// App Service
resource appService 'Microsoft.Web/sites@2023-01-01' = {
name: naming.appService
location: resourceGroup().location
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true
siteConfig: {
alwaysOn: environment != 'dev'
appSettings: [
{
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
value: appInsights.properties.InstrumentationKey
}
{
name: 'Environment'
value: environment
}
]
}
}
}
// SQL Server
resource sqlServer 'Microsoft.Sql/servers@2023-05-01-preview' = {
name: naming.sqlServer
location: resourceGroup().location
properties: {
administratorLogin: sqlAdministratorLogin
administratorLoginPassword: sqlAdministratorPassword
publicNetworkAccess: 'Disabled'
}
}
// Outputs
output appServiceUrl string = 'https://${appService.properties.defaultHostName}'
output sqlServerFqdn string = sqlServer.properties.fullyQualifiedDomainNameBicep-Specific Features
String Interpolation:
var resourceName = 'vm-${projectName}-${environment}'
var customScript = '''
#!/bin/bash
echo "Hello from ${environment}"
'''Symbolic References:
// Direct property access
output storageAccountId string = storageAccount.id
output storageEndpoint string = storageAccount.properties.primaryEndpoints.blobParameter Files
using './main.bicep'
param projectName = 'webapp'
param environment = 'dev'
param sqlAdministratorLogin = 'sqladmin'
param sqlAdministratorPassword = 'YourSecureP@ssw0rd!'Deployment Commands
# Deploy using Bicep
az deployment group create \
--resource-group rg-webapp-dev \
--template-file main.bicep \
--parameters dev.bicepparam
# What-if deployment
az deployment group what-if \
--resource-group rg-webapp-dev \
--template-file main.bicep \
--parameters dev.bicepparamMigration Strategy
Automated Conversion:
# Convert ARM template to Bicep
az bicep decompile --file template.jsonBest Practices:
- Use symbolic references instead of reference() functions
- Implement user-defined types for complex objects
- Leverage improved loop syntax
- Start with new resources in Bicep
- Convert modules one at a time
What’s Next?
In Part 5, we’ll explore advanced Bicep features including modules, registries, and sophisticated deployment patterns.
This is Part 4 of our 7-part series on Infrastructure as Code with ARM Templates and Bicep.
