Introduction

Azure Bicep is a new declarative Domain Specific Language (DSL) for provisioning the Azure resources. The purpose of Azure Bicep is to simplify the authoring experience with a cleaner syntax and the potential for more code re-use. This new language aims to make it easier to write Infrastructure as Code (IaC) for developers and DevOps engineers that typically author ARM templates targeting Azure Resource Manager (ARM) and writing the syntax’s more manageable than the JSON syntax of Azure ARM Templates.

Azure Bicep works as an abstraction layer built over the ARM and ARM Templates. All resource types, apiVersions, and properties valid in an ARM template are equally valid in Azure Bicep. We can do anything with Azure ARM Templates with Azure Bicep as it provides a “transparent abstraction” over ARM (Azure Resource Manager).

Azure Bicep introduces a new file type referred to as a ‘bicep file.’ This file includes code written in the new bicep language. It has a file extension of ‘.bicep.’ Bicep compiles the bicep file to standard ARM Template JSON files ARM JSON is effectively being treated as an Intermediate Language (IL).

Azure Bicep Language

Figure 1: Bicep Structure – Image Source: Intro to Project Bicep (ARM DSL)

Why Azure Bicep?

ARM templates are written in a modified JSON format that can be complicated to write, especially for cloud engineers that are new to working in Azure. Azure Bicep is a Domain Specific Language (DSL) that offers a simplified authoring experience by employing a “transparent abstraction” over ARM templates. The result is cleaner code syntax with better support for modularity and code re-use.

Azure Bicep Goals

Primary goals of the Bicep language, as described in on the project site –https://github.com/Azure/bicep:

  1. Azure Bicep code should be easily understood and straightforward to learn for those both new and experienced with other programming languages.
  2. The Azure Bicep language is a transparent abstraction that does not require any updates or onboarding to the underlying platform to support a new Azure resource type and/or apiVersion.
  3. Create a better language for writing Infrastructure as Code (IaC) to describe, validate, and deploy Azure resources.
  4. Code re-use should be a primary feature allowing users the freedom to modularize and re-use code without ‘copy/paste’.
  5. Azure Bicep should enable users to have high confidence that the code is ‘syntactically valid’ before it’s deployed.
  6. Tooling should provide a high level of resource discoverability and validation and should be developed alongside the compiler rather than added at the end.

Azure Bicep Limitations

Azure Bicep is a new project, and some known limitations should be kept in mind at this time:

  1. No support for the copy or condition property.
  2. No explicit support for deployments across scopes Bicep assumes we are deploying to a resource group, though the generated template can be deployed to any scope.
  3. Single line object and arrays (i.e. [‘a’, ‘b’, ‘c’]) are not yet supported.
  4. We still need to deploy the compiled template with the help of PowerShell Az deployment cmdlets and az cli deployment commands.
  5. Minimal resource schema validation. Other than basic validations like correct resource type structure and requiring a name, we will not get errors for missing or incorrect properties in a resource declaration.
  6. No support for string interpolation in property names.

Install Azure Bicep

The critical component for Azure Bicep is the Bicep CLI. This is the required tool used for compiling Bicep code into ARM JSON. It’s open-source and cross-platform.
The Azure Bicep project also has a Visual Studio Code Extension for Azure Bicep. This extension adds Azure Bicep and .bicep file support to Visual Studio Code. This will enhance the experience when authoring .bicep files. This extension is developed along-side the Azure Bicep project since the tooling is being developed simultaneously as the Azure Bicep compiler.

Please refer to this section for detailed instructions to install the Azure Bicep.

Azure Bicep Files and Syntax

Azure Bicep code is written in a simpler syntax that is easier to read and write than the JSON syntax used with ARM Templates. The Azure Bicep code’s main element is the resource block that declares an infrastructure resource to provision.

Resource Declaration Syntax

  1. resource keyword.
  2. symbolic name – This is an identifier for referencing the resource throughout the bicep file. It is not what the name of the resource will be when it’s deployed.
  3. type – This is composed of the resource provider, resource type, and apiVersion.
  4. properties – These are the specific properties to specify for the given resource type. These are the same properties available in an ARM Template.

Declaring Azure resources with Azure Bicep code is done using the following format:

resource <symbolic-name>'<type>@<api-version>' = {
    name: 'uniquestorage001' // must be globally unique
    location: 'eastus'
    kind: 'Storage'
    sku: {
        name: 'Standard_LRS'
    }
}

Here’s a simple example of an Azure Bicep code that deploys an Azure Storage Account:

resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = {
name: 'uniquestorage001' // must be globally unique
    location: 'eastus'
    kind: 'Storage'
    sku: {
        name: 'Standard_LRS'
    }
}

Parameter Syntax

In Azure Bicep, we can also declare parameters in the .bicep file. These parameters can be required to be passed in when the template is deployed, or the parameters can be given a default value if they aren’t passed in.
Here’s an example of the Azure Bicep code to use within a .bicep file to declare the parameters with default values assigned and declaring an Azure Storage Account resource to deploy with the storageAccountName and location properties getting assigned to the values of the parameters declared:

param location string = 'eastus'
param name string = 'uniquestorage001' // must be globally unique

resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = {
    name: name
    location: location
    kind: 'Storage'
    sku: {
        name: 'Standard_LRS'
    }
}

Variable Syntax

Azure Bicep variables are defined by using the var keyword followed by the variable name, then followed by the equal sign (=) and the value to assign to the variable. This is done using the following format:

var <variable-name> = <value>

Here’s an example of declaring a variable named storageSkuName with a string value set and an Azure Storage Account resource to deploy with the storage Sku name property getting assigned to the value of the variable declared:

var storageSku = 'Standard_LRS' // declare variable and assign value

resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = {
    name: name
    location: location
    kind: 'Storage'
    sku: {
        name: storageSku // reference variable
    }
}

Output Syntax

In Azure Bicep, output variables are declared by using a similar syntax to variables used within the template but using the output keyword for declaration.
Here’s an example of declaring an output variable assigned to the value of the Azure Resource Id for the resource with the symbolic name of mystorage within the .bicep file:
// Output variable set to Azure Resource Id of Storage Account

output storageId string = stg.id // output resourceId of storage account

Azure Bicep Expressions

Expressions in Azure Bicep support the same built-in functions and expressions as ARM Templates.

Functions

Any valid ARM Template function is also a valid bicep function. Just like in ARM Templates, we can use the uniqueString() function to generate a unique string value to use within the template.
Here is an example of valid functions. Instead of forcing users to guess a unique storage account name in our main.bicep file, let’s get rid of our name parameter and use the uniqueString() and resourceGroup() functions to calculate a unique name.

param location string = resourceGroup().location

var storageSku = 'Standard_LRS' // declare variable and assign value

resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = {
    name: uniqueString(resourceGroup().id) // generates unique name based on resource group ID
    location: location
    kind: 'Storage'
    sku: {
        name: storageSku // assign variable
    }
}

output storageId string = stg.id

String Interpolation

Azure Bicep supports a simpler syntax for performing string concatenation like this with a programming feature called string interpolation. String interpolation allows to use a syntax to embed the name of a variable or parameter within a string value that will get replaced at deploy time to perform the string concatenation necessary.
Here is an example where we can combine a namePrefix parameter with a hardcoded suffix:

param namePrefix string = 'unique'

var storageAccountName = '${namePrefix}storage001'

Conditional Assignment

In Azure Bicep, we can conditionally provide a value for a variable, resource, or output using the ternary operator, which is the equivalent of the if() function in ARM Templates. The use of conditional and property assignments allows for more flexible deployment customization with Azure Bicep based on template parameters.
Here’s the example where we choose a redundancy setting for our storage account by adding a new parameter globalRedundancy and combining it with the ternary operator.

param location string = resourceGroup().location
param namePrefix string = 'stg'

param globalRedundancy bool = true // defaults to true, but can be overridden

var storageAccountName = '${namePrefix}${uniqueString(resourceGroup().id)}'

resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = {
    name: storageAccountName
    location: location
    kind: 'Storage'
    sku: {
        name: globalRedundancy ? 'Standard_GRS' : 'Standard_LRS' // if true --> GRS, else --> LRS
    }
}

output storageId string = stg.id

Compiling and Deploying Azure Bicep Code

Azure Bicep code is written in files with the .bicep extension. These Bicep files will contain the code that will then be compiled into Azure Resource Manager (ARM) JSON. Then, once compiled, the resulting ARM JSON will be deployed to Microsoft Azure using Portal, PowerShell or Azure CLI.

Compiling Azure Bicep

Each of the .bicep files is compiled into a single ARM Template JSON file. If we have Azure Bicep file named main.bicep, Azure Bicep CLI command to compile it into ARM JSON:

bicep build main.bicep

This command will compile the main.bicep file into ARM JSON and save the resulting ARM Template into a file with the same name using a .json file extension. So, compiling main.bicep would result in an ARM Template existing in a new file named main.json in the same directory as the original main.bicep file. The .bicep file will still exist, and we can continue to modify as we build out the Infrastructure as Code.

Deploying Azure Bicep

The ARM Template i.e. main.json that results from Azure Bicep compilation is deployed the same as any other ARM Template. This can be done using either the Azure CLI or Azure PowerShell command-line tools.

Below are the commands that can be used to deploy the resources using Azure PowerShell and Azure CLI.

Azure PowerShell:

bicep build ./main.bicep # generates main.json
New-AzResourceGroup -Name my-rg -Location eastus # optional - create resource group 'my-rg'
New-AzResourceGroupDeployment -TemplateFile ./main.json -ResourceGroupName my-rg

Azure CLI:

bicep build ./main.bicep # generates main.json
az group create -n my-rg -l eastus # optional - create resource group 'my-rg'
az deployment group create -f ./main.json -g my-rg

Take Away

Azure Bicep is more of a revision to the existing ARM template language than an entirely new language. While there have been syntax changes, the core functionality of ARM templates and the runtime remains the same. Bicep is not yet production-ready, but it will make life easier for cloud engineers working in Azure when it matures.
Bicep is moving towards a more stable 0.3 version with features to make authoring ARM templates more straightforward and approachable.