Infrastructure as Code with ARM Templates and Bicep: Part 4 – Introduction to Bicep

Infrastructure as Code with ARM Templates and Bicep: Part 4 – Introduction to Bicep

This entry is part 4 of 7 in the series Infrastructure as Code templates using ARM and Bicep

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.

Navigate<< Infrastructure as Code with ARM Templates and Bicep: Part 3 – Advanced ARM Template PatternsInfrastructure as Code with ARM Templates and Bicep: Part 5 – Advanced Bicep Patterns >>

Written by:

387 Posts

View All Posts
Follow Me :