Azure Bicep is an abstraction built on top of Azure ARM Templates and Azure Resource Manager that offers a cleaner code syntax with better support for modularity and code re-use. Azure Bicep moves away from the JSON syntax used by ARM Templates and is much easier to both read and write Infrastructure as Code (IaC) in Azure! This is the latest tool from Microsoft for deploying Azure resources in a DevOps process, and its even open source!

Let’s get started!

Warning: Before you start using Azure Bicep, know that it is in an early Alpha state and is not supported for use in production environments just yet. Also, it doesn’t support all the functionality it eventually will. However, it does support some really great features already that makes Azure Infrastructure as Code (IaC) even easier to write and read!



What is Azure Bicep?

Azure Bicep is a new declarative Domain Specific Language (DSL) for deploying Azure resources. The goal of this new language is to make it easier to write Infrastructure as Code (IaC) targeting Azure Resource Manager (ARM) using a syntax that’s more friendly than the JSON syntax of Azure ARM Templates.

Azure Bicep works as an abstraction layer built on top of ARM Templates. Anything that can be done with Azure ARM Templates can be done with Azure Bicep as it provides a “transparent abstraction” over ARM (Azure Resource Manager). With this abstraction, all the types, apiVersions, and properties valid within ARM Templates are also valid with Azure Bicep.

Azure Bicep is a compiled / transpiled language. This means that the Azure Bicep code converted into ARM Template code. Then, the resulting ARM Template code is used to deploy the Azure resources. This transpiling enables Azure Bicep to use it’s own syntax and compiler for authoring Azure Bicep files that compile down to Azure Resource Manager (ARM) JSON as a sort of intermediate language (IL).

Get Started with Azure Bicep - Alternative to ARM Templates 3
Azure Bicep language compilation flow

The way that Azure Bicep is transpiled into ARM JSON is similar to how there are many different languages that can be written in, then transpiled into JavaScript that can be run within the web browser. One popular example of this type of transpiled language is TypeScript. A transpiled language offers benefits of adding an abstraction layer to make it easier and / or more feature full to write code that then gets compiled down to IL code that gets executed. This is also similar to how C# and VB.NET code compile down to MSIL in .NET code.

In the development world, it’s common to encounter the use of transpiled languages. It’s also common in the DevOps world where YAML and JSON are converted between one or the other. Azure Bicep offers some similarity in how it’s transpiled into ARM JSON. This enables you to use an alternative syntax and feature set for writing declarative Infrastructure as Code than the often-cumbersome ARM JSON syntax.

Azure Bicep offers some similarity in how it’s transpiled into ARM JSON. This enables you to use an alternative syntax and feature set for writing declarative Infrastructure as Code than the often-cumbersome ARM JSON syntax.

Azure Bicep will have limits in its ability to support features that are not natively supported by ARM Templates when deploying Azure resources. However, Azure Bicep will also be able to implement additional code reuse and other features that the Azure Bicep compiler will be able to translate into valid ARM JSON features. Through all this, Azure Bicep has a goal of offering a cleaner syntax along with better support for modularity and code re-use than is offered by ARM JSON.


Why use Azure Bicep?

Azure Resource Manager and ARM Templates are written in a JSON syntax that can be cumbersome to work with. Azure Bicep is a Domain Specific Language (DSL) that offers a transparent abstraction over Azure Resource Manager and ARM Templates that offers support for a cleaner code syntax with better support for modularity and code re-use. Azure Bicep offers a few improvements for authoring Azure IaC over the use of ARM Template JSON.

Azure Bicep Benefits

Here are the primary benefits of using Azure Bicep that are integrated as core goals into the Azure Bicep project and are the foundations for why Microsoft is building Azure Bicep:

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

Also, Azure Bicep is being specifically designed so that it is not a general-purpose language for meeting any need. It is a Domain Specific Language (DSL) meant for writing declarative IaC. It’s also not meant to provide a model for non-Azure related tasks.


Install Azure Bicep

Azure Bicep is a DevOps tool built for authoring IaC used to deploy Azure resources from any environment. As a result, this means Azure Bicep supports Windows, Linux, and macOS.

The primary component for Azure Bicep is the Bicep CLI. This is the required tool used for compiling Bicep code into ARM JSON; plus, it’s open source and cross-platform.

Let’s take a look at installing Azure Bicep in different environments!

Download Bicep CLI Binaries

When installing Azure Bicep, you will need to first download the Bicep CLI binary built for your operating system. All the binaries for the different supported operating systems can be downloaded from the official releases page of the Azure Bicep open source project.

You can either manually download the Bicep CLI binary and install it, or you can use the following helper script examples to install Azure Bicep more easily.

Install on Windows

On Windows machines, Azure Bicep can be installed using PowerShell with the following script:

# Create the install folder
$installPath = "$env:USERPROFILE\.bicep"
$installDir = New-Item -ItemType Directory -Path $installPath -Force
$installDir.Attributes += 'Hidden'
# Fetch the latest Bicep CLI binary
(New-Object Net.WebClient).DownloadFile("https://github.com/Azure/bicep/releases/latest/download/bicep-win-x64.exe", "$installPath\bicep.exe")
# Add bicep to your PATH
$currentPath = (Get-Item -path "HKCU:\Environment" ).GetValue('Path', '', 'DoNotExpandEnvironmentNames')
if (-not $currentPath.Contains("%USERPROFILE%\.bicep")) { setx PATH ($currentPath + ";%USERPROFILE%\.bicep") }
if (-not $env:path.Contains($installPath)) { $env:path += ";$installPath" }
# Verify you can now access the 'bicep' command.
bicep --help
# Done!

Install on macOS

On macOS machines, Azure Bicep can be installed using the terminal with the following script:

# Fetch the latest Bicep CLI binary
curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-osx-x64
# Mark it as executable
chmod +x ./bicep
# Add Gatekeeper exception (requires admin)
sudo spctl --add ./bicep
# Add bicep to your PATH (requires admin)
sudo mv ./bicep /usr/local/bin/bicep
# Verify you can now access the 'bicep' command
bicep --help
# Done!

Install on Linux

On Linux machines, Azure Bicep can be installed using the following script:

# Fetch the latest Bicep CLI binary
curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64
# Mark it as executable
chmod +x ./bicep
# Add bicep to your PATH (requires admin)
sudo mv ./bicep /usr/local/bin/bicep
# Verify you can now access the 'bicep' command
bicep --help
# Done!

Install in Azure Cloud Shell

The Azure Cloud Shell runs on Ubuntu server, so to install Azure Bicep, you will need to install the Linux binary for the Bicep CLI. However, you cannot simply use the above script for installing Azure Bicep on Linux due to a limitation of the Azure Cloud Shell that prevents you from modifying the PATH.

Here are some steps you can follow to basically “install” Azure Bicep for use in the Azure Cloud Shell environment:

  1. Connect to the Azure Cloud Shell; either within the Azure Portal or at https://shell.azure.com.
  2. First, you likely want to create a directory within the Azure Cloud Shell environment to save the Bicep CLI to. For example, creating a ~/tools directory:
    mkdir ~/tools
  3. Next, navigate to the directory created and download the Linux binary for the Bicep CLI into the Azure Cloud Shell environment into this directory:
# Navigate to "tools" directory
cd ~/tools
# Fetch the latest Bicep CLI binary
curl -Lo bicep https://github.com/Azure/bicep/releases/latest/download/bicep-linux-x64
# Mark it as executable
chmod +x ./bicep

Using the above steps will place the Azure Bicep CLI in the location of ~/tools/bicep within you Azure Cloud Shell environments. Once placed here, you can execute the Bicep CLI from anywhere within the Azure Cloud Shell environment by referencing it within the ~/tools directory.

For example, when located within the ~/bicep directory, you can run the following command to utilize the ~/tools/bicep executable to build some Azure Bicep code in the main.bicep file:

chris@Azure:~/bicep$ ~/tools/bicep build main.bicep

With the Azure Bicep CLI installed into the Azure Cloud Shell environment, then you’ll be able to utilize Azure Bicep easily from anywhere without the need to install it on your local machine.


Compiling and Deploying Azure Bicep code

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

Compiling Azure Bicep

Each of the .bicep files is compiled into a single ARM Template JSON file. If you have a single Azure Bicep file named main.bicep, you can use the following 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 save directory as the original main.bicep file. Keep in mind the .bicep file will still exist as this is the Azure Bicep code you will continue to modify as you build out the Infrastructure as Code solution.

You can also use the Azure Bicep to compile multiple .bicep files into ARM Template JSON with a single command, such as:

bicep build main.bicep second.bicep

Compiling an Empty Azure Bicep File

Let’s take a look at compiling an empty Azure Bicep file before we look at other features and syntax.

When you compile Azure Bicep code, it will automatically include the basic outline (or framework) JSON code of the ARM Template. When you compile an empty Azure Bicep file, the resulting ARM Template will include only this basic JSON outline. As a result, an empty .bicep file is an accepted file for compiling.

An empty .bicep file will be compiled into the following JSON:

{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {},
  "functions": [],
  "variables": {},
  "resources": [],
  "outputs": {}
}

When compiling Azure Bicep code, the Azure Bicep code declared within the .bicep file will be compiled to ARM Template JSON code and included within the empty ARM JSON template shown above in the correct locations.

Deploying Azure Bicep

Azure Bicep is a transpiled language, so you first need to compile it into ARM Template JSON, then the resulting template can be deployed to Microsoft Azure. The ARM Template that results from Azure Bicep compilation is deployed to Microsoft Azure just the same as any other ARM Template. This can be done using either the Azure CLI or Azure PowerShell command-line tools.

Azure CLI Deployment

Here’s an Azure CLI example of compiling Azure Bicep code and deploying it to Azure:

# Transpile / Compile Azure Bicep code
bicep build main.bicep

# Create an Azure Resource Group to deploy to
az group create -n Bicep-RG -l northcentralus

# Deploy ARM Template to Resource Group
az deployment group create -f main.json -g Bicep-RG

Azure PowerShell Deployment

Here’s an Azure PowerShell example of compiling Azure Bicep code and deploying it to Azure:

# Transpile / Compile Azure Bicep code
bicep build main.bicep

# Create an Azure Resource Group to deploy to
New-AzResourceGroup -Name Bicep-RG -Location northcentralus

# Deploy ARM Template to Resource Group
New-AzResourceGroupDeployment -TemplateFile main.json -ResourceGroupName Bicep-RG

Note: Azure Bicep is compiled (or transpiled) into an ARM Template JSON file that is then deployed the same as any other ARM Template. This can be done using the Azure CLI, Azure PowerShell, or within the Azure Portal.


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 code syntax created for Azure Bicep shares some elements that you may already be familiar with in both JSON and YAML code.

The main element of Azure Bicep code is the resource block that declares an infrastructure resource to deploy. Here’s a simple example of Azure Bicep code that deploys an Azure Storage Account:

resource mystorage 'Microsoft.Storage/storageAccounts@2019-06-01' = {
  name: 'bicepstorage2063'   // Globally unique storage account name
  location: 'northcentralus' // Azure Region
  kind: 'Storage'
  sku: {
    name: 'Standard_LRS'
  }
}

Azure Resource Declaration Syntax

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

resource <symbolic-name> '<type>@<api-version>` = {
  // Properties
  name: 'bicepstorage2063'
  location: 'northcentralus'
  sku: {
    name: "Standard_LRS'
  }
}

The different elements (as seen by the placeholders in the above example) of the Azure resource declaration in Azure Bicep code are as follows:

  • resource keyword – Use for declaring the block of code that defines Azure Resources to deploy
  • Symbolic name – This is an identifier (or name) within the Azure Bicep file that can be used to reference this resource in other locations within the bicep file. Keep in mind, this is not the name of the Azure resource that is deployed; this name is only for referencing this resource within the Azure Bicep code.
  • Type – This is the Azure Resource Type name for the resource that is being declared. This is composed of the Azure Resource Provider name (such as Microsoft.Storage), and the resource type (such as storageAccounts). The full Azure resource type value for an Azure Storage Account is Microsoft.Storage/storageAccounts.
  • API Version – After the Azure resource type, separated by an @ character, there needs to be an Azure Resource Provider apiVersion specified. This is a requirement that comes from Azure Resource Manager (ARM) and will be similar to the apiVersion specified in ARM Templates. For example, the API Version specified for an Azure Storage Account could be 2019-06-01.
  • Properties – The resource properties are contained within the = { ... } block within the resource declaration. These will be the specific properties required for the specific Azure resource type declared. All these properties will be the same properties required by an ARM Template when declaring the same Azure resource type.

The combination of the type and api version for the full Azure Resource Manager resource type that the resulting ARM Template will deploy. Both of these values must be specified for all Azure resources declared within Azure Bicep code. Keep in mind that the valid values to use here are the same as what’s accepted within ARM Templates. As a result, Azure Bicep will automatically accept any valid ARM resource type and api version without any updated necessary for Azure Bicep to support it.

Note: If you’re familiar with HashiCorp Terraform, then the Azure Bicep syntax should look a little familiar. While it’s not the same syntax, it does share some similarities. One big difference between the two is that Terraform is a multi-platform targeting IaC toolset, where Azure Bicep is designed specifically for targeting only Microsoft Azure deployment. As a result, Azure Bicep shares many of the “natively built for Azure” features of ARM Templates that Terraform does not share in the same fashion.

Parameter Syntax

Parameters in an ARM Template are variables that are passed into the template at deployment time; generally via a file with the .parameters.json extension; such as main.parameters.json. In Azure Bicep, you can also declare parameters in your .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 a couple parameters with default values assigned:

param location string = 'northcentralus'
param storageAccountName string = 'bicepstorage2063'

Once the parameters have been declared within Azure Bicep code, you can reference them anywhere in the .bicep file where you need to use the parameters value to set an Azure resource property to.

Here’s a various of the above example of declaring an Azure Storage Account resource to deploy with the storageAccountName and location properties getting assigned to the values of the parameters declared:

resource mystorage 'Microsoft.Storage/storageAccounts@2019-06-01' = {
  name: storageAccountName // Globally unique storage account name
  location: location       // Azure Region
  kind: 'Storage'
  sku: {
    name: 'Standard_LRS'
  }
}

Notice the storageAccountName and location parameters are referenced directly by name to set Azure resource properties to their values. This syntax is much simpler to use than the more verbose ARM Template method requiring the use of the “parameters” method; such as [parameters('storageAccountName')].

Variable Syntax

Variables in an Azure Bicep are used to store values and declare complex expressions that can be used one or more times by referencing them throughout the template. The declaration and use of variables in Azure Bicep is the same as how they are used in ARM Templates.

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:

var storageSkuName = 'Standard_LRS'

There are several data types supported for use with variables in Azure Bicep code.

  • string
  • boolean
  • numeric
  • object
  • array

Here are some example of defining these different variable types:

// string
var name = 'myResource'

// boolean
var isTrue = true

// numeric
var count = 2

// object
var myObject = {
  sku: 'Premium'
  instanceCount: 5
}

// array
var myArray = [
  'value 1'
  'value 2'
  'value 3'
]

The above examples show assigning the variable values to static (or hard-coded) values. You can also use some programmability to assign the values of variables using supported methods and / or boolean operations. The methods supported here in the Bicep code is the same as the methods supported within Azure ARM Templates.

Here’s a couple examples of using some of the supported ARM Template methods with Azure Bicep code to assign the values to variables programmatically:

// length of 'name' variable value
var nameLength = length(name)

// array has items
var hasItems = length(myArray) >= 0

// create array from list of items
var items = createArray('one', 'two', 'three')

Output Variable Syntax

Output variables are used to output some values from the template after it is deployed into Microsoft Azure. In Azure Bicep, output variables are declared using a similar syntax to variables used within the template, but using the output keyword for declaration.

Declaring output variables in Azure Bicep is done using the following format:

output <output-variable-name> <data-type> = <value>
  • output variable name – This is the output variable name that will be used to reference this output variable as it’s returned from the output of the template deployment.
  • data type – This defines the data type of the output variable. This supports the same data types as the variable keyword within Azure Bicep.
  • value – This defines the value to assign to the output variable. This could be a hard-coded value, but most likely will be assigned to an expression or other Azure resource property reference.

Here’s an example of declaring an output variable that is 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 storageAccountId string = mystorage.id

Notice that the .id property is being referenced on the Azure resource to retrieve the Azure Resource Id. In Azure Bicep, this .id property is a shorthand supported for the use of the resourceId() method call within an Azure ARM Template. This allows you to more easily reference the Resource Id of the Azure resource within the Azure Bicep code as a simple .id property reference.


Expressions

Expressions in Azure Bicep support the same built-in functions and expressions as ARM Templates. If you’re familiar with the built-in functions of ARM Templates, this will make it easier for you to jump in to authoring Azure Bicep files. Also, if you’re still new to using the built-in functions of ARM Templates, then you can reference the ARM Template documentation for the functions that can be used in Azure Bicep expressions too.

Function Expressions

Here are a few examples of valid function call expressions that can be used in Azure Bicep:

// Set parameter to Resource Id for existing Web App in Azure
param webAppResourceId string = resourceId('Microsoft.Web/sites', 'b59webapp')

// Set variable to default location for the Resource Group deployed to
var location = resourceGroup().location

// Set output variable to uppercase of 'storageAccountName' value
output upperName string = toUpper(storageAccountName)

Just like in ARM Templates, you can use the uniqueString() function to generate a unique string value to use within the template. By passing in the .id of the Resource Group being deployed, the unique string value generated will be deterministic based on the resource group. This enables for the same unique string value to be generated every time the template is deployed to the same resource group within Azure.

Here’s an example of using the uniqueString() function in an expression for assigning the name property of an Azure Storage Account resource deployed to Azure:

resource mystorage 'Microsoft.Storage/storageAccounts@2019-06-01' = {
  name: uniqueString(resourceGroup().id) // Globally unique storage account name
  location: location       // Azure Region
  kind: 'Storage'
  sku: {
    name: 'Standard_LRS'
  }
}

String Interpolation

One of the most commonly used built-in functions of ARM Templates is the concat() function. This function enables you to concatenate multiple strings together into a single string value.

Here’s the ARM Template expression for using the concat function to concatenate the value of the resourcePrefix parameter with the value of appstorage:

{
  "variables": {
    "storageAccountName": "[concat(parameters('resourcePrefix'), 'appstorage')]"
  }
}

Azure Bicep supports a simpler syntax for performing string concatenation like this with a programming feature called string interpolation. String interpolation allows you 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’s the same example as above written in Azure Bicep using string interpolation:

param resourcePrefix string = 'contoso'

var storageAccountName = '${resourcePrefix}appstorage'

At deploy time this code will result in the storageAccountName variable being set to the value of contosoappstorage, or slightly different if a different resourcePrefix parameter gets passed into the template when deployed.

Notice the string interpolation uses the syntax of ${<expression>} to define the expression that defines the value to place in that location within the string. The expression within can be as simple as a variable or parameter name, or even the use of a built-in function to retrieve another value.

Here’s a similar example that uses the uniqueString() function the expression to define the second part of the variables string value:

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

Conditional Assignment

The ternary operator can be used to conditionally assign values to variables and properties within Azure Bicep. This is essentially the equivalent of performing an if ... else in programming, and the if() function in an ARM Template.

The use of conditional variable and property assignments allows for more flexible deployment customization with Azure Bicep based on template parameters, or feature flags.

Here’s an example of using the ternary operator to conditionally set the sku.name of an Azure Storage Account to either Geo-Replicated Storage or Local-Replicated Storage based on the globalReplication boolean parameter passed into the template during deployment. This essentially works as a feature flag to conditionally set the type of Azure Storage Account to deploy.

// Parameter for Feature Flag
param globalReplication bool = true
// defaults to true
// can be overridden by passing in different parameter value

// deploy Azure Storage Account
resource mystorage 'Microsoft.Storage/storageAccounts@2019-06-01' = {
  name: uniqueString(resourceGroup().id) // Globally unique storage account name
  location: location                     // Azure Region
  kind: 'Storage'
  sku: {
    // if true, use Geo-Redundant Storage
    // if false, use Local-Redundant Storage
    name: globalReplication ? 'Standard_GRS' : 'Standard_LRS'
  }
}

Azure Bicep Extension for VS Code

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 your experience when authoring .bicep files. This extension is developed along-side the Azure Bicep project since the tooling is being developed at the same time as the Azure Bicep compiler.

You can install this extension manually with these steps from within Visual Studio Code:

  1. Download the Azure Bicep extension for VS Code from https://github.com/Azure/bicep/releases/latest/download/vscode-bicep.vsix
  2. Open the Extension tab within Visual Studio Code
  3. Select the ellipse ... to open the options menu in the top right corner, then select the Install from VSIX option.
  4. Select the downloaded .vsix file for the Azure Bicep extension
Get Started with Azure Bicep - Alternative to ARM Templates 4
Azure Bicep extension for Visual Studio Code

Wrap Up

Overall, there are a few more features supported by Azure Bicep than what is listed above in this “getting started” style article. Hopefully this helps give you a better understanding of the basics of authoring Azure Bicep files for declaring Azure resource deployment more simply than using the ARM Template syntax. Also, keep in mind as Azure Bicep is developed it will be enhanced with more features to help make authoring Azure IaC code easier and more readable.

Microsoft MVP

Chris is the Founder of Build5Nines.com and a Microsoft MVP in Azure & IoT with 20 years of experience designing and building Cloud & Enterprise systems. He is also a Microsoft Certified: Azure Solutions Architect, developer, Microsoft Certified Trainer (MCT), and Cloud Advocate. He has a passion for technology and sharing what he learns with others to help enable them to learn faster and be more productive.