Back to Blog
3 min read

Azure Bicep in 2022: The New Standard for Infrastructure as Code

Azure Bicep has matured significantly and is now the recommended way to author ARM templates. Let’s explore the latest features and best practices for 2022.

Why Bicep?

Bicep offers:

  • Cleaner syntax than ARM JSON
  • First-class tooling in VS Code
  • No state management (unlike Terraform)
  • Direct integration with Azure
  • Module support for reusability

Getting Started

# Install Bicep CLI
az bicep install
az bicep upgrade

# Check version
az bicep version

Modern Bicep Patterns

Resource Organization

// main.bicep
targetScope = 'subscription'

param environment string
param location string = 'australiaeast'

// Resource group
resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: 'rg-myapp-${environment}'
  location: location
  tags: {
    environment: environment
    managedBy: 'bicep'
  }
}

// Deploy resources to the resource group
module resources 'modules/resources.bicep' = {
  name: 'resources-deployment'
  scope: rg
  params: {
    environment: environment
    location: location
  }
}

output resourceGroupId string = rg.id
output appServiceUrl string = resources.outputs.appServiceUrl

Loops and Conditions

param storageAccounts array = [
  { name: 'logs', sku: 'Standard_LRS' }
  { name: 'data', sku: 'Standard_GRS' }
  { name: 'backup', sku: 'Standard_RAGRS' }
]

param deployRedis bool = true

resource storage 'Microsoft.Storage/storageAccounts@2021-08-01' = [for account in storageAccounts: {
  name: '${account.name}${uniqueString(resourceGroup().id)}'
  location: location
  sku: {
    name: account.sku
  }
  kind: 'StorageV2'
  properties: {
    minimumTlsVersion: 'TLS1_2'
    supportsHttpsTrafficOnly: true
  }
}]

resource redis 'Microsoft.Cache/redis@2021-06-01' = if (deployRedis) {
  name: 'redis-${uniqueString(resourceGroup().id)}'
  location: location
  properties: {
    sku: {
      name: 'Basic'
      family: 'C'
      capacity: 0
    }
  }
}

User-Defined Types (2022 Feature)

@description('Configuration for the web application')
type webAppConfig = {
  name: string
  @minValue(1)
  @maxValue(10)
  instanceCount: int
  sku: 'F1' | 'B1' | 'S1' | 'P1v3'
  customDomain: string?
}

param config webAppConfig

resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
  name: 'plan-${config.name}'
  location: location
  sku: {
    name: config.sku
    capacity: config.instanceCount
  }
}

Decorators

@description('The name of the storage account')
@minLength(3)
@maxLength(24)
param storageAccountName string

@description('The SKU of the storage account')
@allowed([
  'Standard_LRS'
  'Standard_GRS'
  'Standard_RAGRS'
  'Premium_LRS'
])
param storageSku string = 'Standard_LRS'

@secure()
param adminPassword string

@metadata({
  author: 'Michael John Pena'
  version: '1.0.0'
})
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: storageSku
  }
  kind: 'StorageV2'
}

Modules

Create reusable modules:

// modules/appService.bicep
param name string
param location string = resourceGroup().location
param sku string = 'S1'
param linuxFxVersion string = 'DOTNETCORE|6.0'

resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
  name: 'plan-${name}'
  location: location
  kind: 'linux'
  properties: {
    reserved: true
  }
  sku: {
    name: sku
  }
}

resource appService 'Microsoft.Web/sites@2021-03-01' = {
  name: name
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    siteConfig: {
      linuxFxVersion: linuxFxVersion
      alwaysOn: sku != 'F1'
    }
    httpsOnly: true
  }
}

output url string = 'https://${appService.properties.defaultHostName}'
output principalId string = appService.identity.principalId

CI/CD Integration

# azure-pipelines.yml
trigger:
  branches:
    include:
      - main

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: Validate
    jobs:
      - job: Validate
        steps:
          - task: AzureCLI@2
            inputs:
              azureSubscription: 'Azure-Connection'
              scriptType: 'bash'
              scriptLocation: 'inlineScript'
              inlineScript: |
                az bicep build --file main.bicep

                az deployment sub what-if \
                  --location australiaeast \
                  --template-file main.bicep \
                  --parameters environment=dev

  - stage: Deploy
    dependsOn: Validate
    jobs:
      - deployment: Deploy
        environment: 'Production'
        strategy:
          runOnce:
            deploy:
              steps:
                - task: AzureCLI@2
                  inputs:
                    azureSubscription: 'Azure-Connection'
                    scriptType: 'bash'
                    scriptLocation: 'inlineScript'
                    inlineScript: |
                      az deployment sub create \
                        --location australiaeast \
                        --template-file main.bicep \
                        --parameters environment=prod

Bicep in 2022 is production-ready and the best choice for Azure infrastructure as code.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.