An updated guide on Azure Bicep


Posted on 7/5/2022


Some time ago I posted this guide on Bicep templates. As some time has passed I wanted to update and extend this in a full guide on Azure Bicep.

What is Bicep?

Bicep is a domain-specific language (DSL) developed by Microsoft to reduce the complexity of Azure ARM Templates. Bicep is transparent abstraction for Azure ARM Templates. It does not change the way Resources are deployed in Azure. It provides any functionality ARM templates have but aims to simplify the definition.

How to start

To use Azure Bicep, head over to GitHub and get the latest version of the Bicep CLI.

If you use winget package manager you can also use winget install -e --id Microsoft.Bicep to install the CLI.

Alternatively, you can also use the Azure CLI to install bicep. However, this would change the commands used in this article. Please see the docs article for more information.

If you don't have VSCode already, I highly recommend using it as there is this amazing extension for Bicep.

How does Bicep works?

You will start writing a .bicep file. After you are finished, this will be converted using the Bicep CLI to Azure ARM Templates. Those templates will then be deployed using the same way, you would normally deploy ARM Templates.

Bicep Architecture Diagram. Showing the conversion from bicep over arm to Azure Resource Manager API

Language

The language uses similar modules as ARM (parameter, variables, resources, etc.). For someone who does know already ARM Templates, those might be quite familiar. However, certain details are different.

> Note: Different than json, bicep doesn't use commas to separate arrays and uses single quotes ' instead of double quotes " for strings. You will see some examples below

Parameters

Similar to ARM we can provide parameters. Those values need to be specified when deploying. Parameters will be declared using the param myParameterName TYPE syntax. You can also define default values to a parameter as shown below.

param myString string
param myInt int
param myBool bool
param myArray array
param myObject object

param myDefaultString = 'default value'
param location = resourceGroup().location

Variables

Variables work also the same way as they do in ARM Templates. You specify the name and then an expression or value which will be determined before the deployment starts.

var myString = 'my string'

var location = ' resourceGroup().location

var myObject = {
    first: 'some value'
}

Decorators

Decorators provide a way to attach constraints and metadata to a parameter. They are specified above a parameter using the @expression() syntax. Decorators can be used to specify a string as a password or define the length of a parameter

@secure()
@minLength(12)
@maxLength(26)
param myPassword string

@allowed([
  'one'
  'two'
])

Resource

The major part of the template is the resource part. Here you can define an Azure resource.

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
  name: st
  location: location 
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
 }
properties: {
  supportsHttpsTrafficOnly: true
}
}

If you have used ARM or Terraform before, the SKU and Properties section and the options there will be quite familiar.

Something is different to ARM, that you will provide every resource a name. This name can be used to reference this resource. This name must be unique within one template file. It does not need to be unique in Azure, however. It is only used within Bicep.

Referencing resources

Resources or properties can be referenced using the name we gave a resource.

resource myStorageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
  name: 'stmyname'
  location: location 
  kind: 'StorageV2'
}

The resource id of the storage account can now be referenced using ```myStorageAccount .Id``

In some cases a resource can also be specifying directly for example when referencing a parent resource or for example in a depends on.

Note: DependsOn will automatically be set if you specify a resource. A ```DependsOn`` is only needed if the resource, which creation you want to await, is not directly referenced.

resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2021-09-01' = {
  name: 'default'
  parent: myStorageAccount 
}

Existing

Using the existing keyword, a resource that is not defined in the template can be referenced. The referenced resource must be in the same deployment scope.

resource myKeyVault 'Microsoft.KeyVault/vaults@2021-10-01' existing  = {
 name: 'mykeyvaultname'
}

The KeyVault in this example, which was created can now be referenced. Which is quite easier than dealing with the resourceId() function.

resource keyVaultAcl 'Microsoft.KeyVault/vaults/accessPolicies@2021-10-01' = {
  parent: myKeyVault 
  name: 'replace'
  properties: {
 ...
 }
}

Loops

Loops can be used to iterate a resource definition multiple times

param storageAccounts array

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = [for st in storageAccounts  : {
  name: st
  location: location 
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
 }
}]

Conditions

Conditions can be used to deploy a resource based on a value or expression.

param myBool bool

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = if(myBool)  : {
  name: 'stmyaccount001'
  location: location 
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
 }
}]

Outputs

Outputs are working quite the same as they do in ARM.

resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
  name: 'stmyaccount001'
  location: location 
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
 }
}

output myStorageAccountName string = storageAccount.name

Modules

Modules can be used to separate a template into multiple files. This can be useful to reduce the complexity of a single file. Within a main file, multiple modules can be defined and parameters could be passed between those using the Outputs option.

Each module will be one deployment. So resources defined in multiple modules will be deployed separately. This can cause challenges but could also resolve circle dependencies.

Note: Bicep supports sub-folders. You could use a subfolder to separate the module files.

A Module can be called using the following syntax

module serviceBus 'servicebus.bicep' = {
  name: 'myDeploymentName'
  params: {
     myParam: 'myValue'
} 
}

Note: A good practice is to use unique deployment names. Otherwise you will overwrite the deployment history of a resource group. This could be achieved by including the timestamp. E.g. using the following function utcNow('yyyMMdd-hhmmss')

Scope

Last but not least - Deployment Scopes

Scopes can be used to associate a scope for the deployment. Possible Scopes are Resource Group, Subscription or Management Group Deployments.

modules 'mgModule.bicep' = {
  name: 'myDeploymentName'
  scope: managementGroup('myManagementGroupId')
}

Build and deploy

Bicep files can be compiled into ARM templates using the az cli with the bicep tools installed.

az bicep build --file myBicepFile.bicep

This will create a file called myBicepFile.json, which is the compiled bicep code as ARM Template. Afterwards, the ARM Template can be used for deployment.

az deployment group create --resource-group exampleRG --template-file myBicepFile.bicep --parameters storageName=uniquename

Azure DevOps Pipelines

The same can be achieved in Azure DevOps Pipelines. The following task will compile the bicep file.

  - task: AzureCLI@2
    displayName: 'Build Bicep file'
    inputs:
      azureSubscription: $(azureServiceConnection)
      scriptType: 'pscore'
      scriptLocation: inlineScript
      inlineScript: az bicep build --file $(Build.SourcesDirectory)/myBicepFile.bicep

This file can then be deployed using the AzureResourceGroupDeployment task.

  - task: AzureResourceGroupDeployment@2
    displayName: 'Azure Deployment: Deploy ARM Template'
    inputs:
      azureSubscription: '$(azureServiceConnection)'
      resourceGroupName: '$(resourceGroupName)'
      location: '$(resourceGroupLocation)'
      csmFile: '$(System.SourcesDirectory)/myBicepFile.json'
      deploymentMode: Incremental