Skip to content
Back to Blog
1 min read

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

I wrote “Azure Bicep in 2022: The New Standard for Infrastructure as Code” to share practical, production-minded guidance on this topic.

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.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Peña

Michael John Peña

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