
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-bicep
Basic 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.name
Converting 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.fullyQualifiedDomainName
Bicep-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.blob
Parameter 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.bicepparam
Migration Strategy
Automated Conversion:
# Convert ARM template to Bicep
az bicep decompile --file template.json
Best 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.