Project Bicep: ARM Templates Re-Imagined

This week saw a very exciting new Github repo launched, Project Bicep. Bicep is a project from the ARM template team (Bicep being a play on ARM) to create a new Domain Specific Language (DSL) for working with Azure and ARM templates, to replace the current JSON based language.

ARM templates have long had a reputation for being difficult for newcomers to learn and get started with. The verbose JSON language can lead to complex templates which are challenging to read. Bicep is an attempt to simplify the language, provide a better authoring experience, and better support for modularity. Let’s take a more in-depth look at Bicep.

Experimental Warning

Before we get started, one thing to be very clear on is that Bicep is in the experimental stage, it is going to change and break as it is developed. The current repo is there for you to try out the language, play with it and submit feedback. What it definitely is not, is ready for production use, you should not use it for any production workloads.

Bicep is an Abstraction

The first thing to realise about Bicep is that it is not a re-write of ARM templates, it is an abstraction over the top of ARM. When you write a Bicep template, you use the Bicep language, but when you are ready to run the template you compile, which actually creates an ARM template, which you then deploy. Bicep is effectively an Intermediate Language (IL).

Because Bicep is an abstraction on top of ARM, it has all the features of ARM already, there is no requirement for resources, functions etc. to be adapted or recreated for Bicep, they should just work. That said, there are some temporary limitations in the current preview.

Installing Bicep

To get started with Bicep you should install both the Bicep CLI and the VS Code extension. Full instructions can be found here.

Writing Templates with Bicep

Let’s take a look at what a Bicep template looks like. We’ll open VS Code and create a new file with a .bicep extension. We’ll create a template that takes a couple of parameters and uses these to create a storage account.

param storageAccountName string
param StorageAccountSku string = 'standard_LRS'

resource stg 'Microsoft.Storage/storageAccount@2019-06-01' = {
  name: storageAccountName
  location: resourceGroup().location
  sku : {
    name: StorageAccountSku
  }
}

If you’ve ever used Terraform this will seem somewhat similar, the language constructs are quite alike. Let’s look at the different parts.

First up, we are creating a couple of parameters:

param storageAccountName string
param StorageAccountSku string = 'standard_LRS'

Parameters are defined with the keyword “param”, the name of the parameter and the type. We also have the option of defining a default value, which we have done with the StorageAccountSku parameters. We do also have the option to set other values as we could with ARM, like allowed values, description etc. but to do this, we have to use a different syntax:

param storageAccountSku string {
    default: 'standard_LRS',
    allowed: [
        'standard_LRS'
        'standard_GRS'
    ]
}

Next, we create the actual resources. The first line of the resource declares a few things:

resource stg 'Microsoft.Storage/storageAccount@2019-06-01' = {

This has a few components:

  1. “resource” - defines that this is a resource component
  2. “stg” - the name for the resource in the template, this is not the name the resource will get in Azure, just the name it will be referred to in the template. We will use this later if we want to use values from this resource in another resource.
  3. ‘Microsoft.Storage/storageAccount@2019-06-01’ - this defines the type of resource to create using the resource type identified, and then the name and date of the storage API version to use. This differs from something like Terraform, where it selects the API version to use, here this is under your control.

Finally, the rest of the resource defines the properties needed for those specific resources. You’ll note that for the location we are using a function, ‘resourceGroup()’ to get the location of the resource group. All the existing ARM functions are fully working in Bicep.

This is a fully functional simple Bicep template, but there are also a few other language concepts you should be aware of:

  • Parameters and variables are referenced directly by name, no need to use the parameters function
  • Bicep includes a concept of string interpolation to allow you to mix strings and functions, using the ${} syntax, e.g. ‘${storageAccountName}-001’
  • Bicep supports the ternary operator for conditions
  • Resource names can be used to reference a resource and get properties. For example, if we wanted to get the ID of the storage account, we could just use stg.id.
  • The resource name can also be used to retrieve run time properties as well, such as stg.properties.primaryEndpoints.blob
  • Where resources reference other resources, by using the resource name, a dependency is automatically created

Deploying a Bicep Template

As mentioned, Bicep files compile into ARM templates. This means that, at least currently, there is no way to deploy a Bicep template directly. Instead, you use the Bicep CLI to compile the template, using this command:

bicep build filename.bicep

This will produce an ARM template called filename.json. At this point, we can just deploy this using the standard New-AzResourceGroupDeployment command. If we want to specify parameters, we can create a parameters json file, or pass them in at the command line, so long as your Bicep template is configured to accept these parameters. There is not currently a way to define parameter files in the Bicep language, you need to use JSON.

Coming Up

The initial release of Bicep is mostly about a new language for current features; however, there have already been some new features discussed that will be coming in the future:

  • Modules - being able to create re-usable modules that encapsulate resources and allow you to share them with your team
  • Loops - new loop types, beyond just copy, such as For and For Each

My View on Bicep

I think this is a significant step forward for ARM. Whilst there are already other languages out there like Terraform and Pulumi, there are lot’s of people using ARM, and in particular, people coming to ARM for the first time, and even as their first time dealing with Infrastructure as Code. Simplifying the language will make it much easier for those new to Azure, IaC or ARM to get started and create templates.

I’ve seen several comments from experienced ARM users who have expressed some disappointment in Bicep, in that it doesn’t do enough for their use cases. In my view, this isn’t really what Bicep is about, at least initially. If you’ve been working with ARM for years and are familiar with the JSON language, then Bicep might not be a revolution. The new language certainly will reduce the amount you have to write, and you’ll be able to achieve whatever you can do in ARM in Bicep. That said, once we start seeing features like modules, this should be a big step forward even for advanced users.

I’m not sure yet that I am sold on the compiling to ARM and running the JSON template to deploy, I think it would be good to hide a lot of that complexity and allow a user juts to trigger a deployment straight from Bicep. Hopefully, that is something that will come in the future.

All in all, I think Bicep is a big step forward, and I’m excited about where things are going to go and how Bicep is going to evolve over the next few months. If you’re interested in trying out Bicep and feeding back to the team, then I encourage you to check out the repo and start playing with it.

Further Reading