Infrastructure as Code (IaC) is an important technique when it comes to automation. Teams are starting their journey towards cloud-native often by automating everything. Infrastructure is no exception here. The continuously growing adoption of cloud services and digitalization are just some reasons why IaC tools like Terraform are so popular.
Project Bicep is the new kid on the block. Bicep is a new language that is created to express Azure infrastructures as code.
Scope of the comparison
This article compares Project Bicep and HashiCorp Terraform. It contains
- evaluation of differences between Bicep and Terraform
- comparison of
- deployment methods
- language syntax
- tooling
This article does not contain an introduction to Biceps syntax. The product team and the vivid open-source community provide a great tutorial and an impressive amount of samples. You should invest some time to complete the tutorial and browse through some examples to get a fundamental understanding of the language itself. My friend Tobias Zimmergren also published a great article on Bicep. It is worth reading.
What is Bicep
Bicep is a fairly new, declarative domain-specific language (DSL) invented by Microsoft. It transpiles down to Azure ARM templates. That is why you have access to all resources, their properties, in all available versions. Compared to underlying ARM templates, developers can express Azure infrastructures with way less code. The team tracks the following high-level goals publicly:
- Bicep will provide the best user experience when it comes to deploying, describing, and validating Azure infra
- Bicep should be a transparent abstraction on top of the underlying cloud platform. It should not require any kind of “opt-in” for new Azure services or new service versions
- Bicep language should be easy to read, learn and understand for every developer
- Refactoring, structuring, and modularizing should be straight forward and flexible
- Productivity and rich tooling is being created in parallel with the Bicep compiler to ensure a great developer experience
- Tooling should drive user confidence. Developers should know if their source is valid before deploying it
What is Bicep not
The Bicep team points out what they don’t want to build:
- Bicep will not become a general-purpose language. Which means you will often need some framing. Think of scripts that run before or after a Bicep-based deployment
- Bicep will not provide a first-class provider model for non-Azure-related tasks. Although it is technically possible. The team is focusing on Azure
Bicep compared to Terraform
In the following paragraphs, Bicep and Terraform are compared using three key metrics:
- Deployment methods
- Language syntax
- Tooling
Deployment methods
Terraform uses desired state configuration (DSC). Terraform invokes the underlying cloud infrastructure APIs and mutates the target environment (e.g., Azure) to match the desired state defined by the user. Every Terraform project has a lifecycle, and Terraform CLI assists users while creating, mutating, and destroying infrastructures.
In contrast, Bicep defaults to incremental deployments like underlying ARM Templates. Incremental deployments may add or modify Azure services on a configurable scope (Resource Group, Subscription, Management Group, or Tenant). Relying on incremental deployments, Bicep will never delete a service instance from Azure when you remove it from your source code.
Additionally, you can deploy Bicep projects using the complete deployment mode. Deployments created with complete
mode will result in removing all resources from the targeting scope, which are not part of the applied template. This may also affect resources that are not in the scope of the Bicep project but belong to the same Azure Resource Group.
Comparing both, Bicep (using the incremental deployment mode) is more robust in the first place. One can not accidentally delete a production service by removing some code lines from the source code. However, this results in Bicep leading to potential configuration drift. Configuration drift is the difference between the state described in the codebase and the actual infrastructure.
On the flip side, every Terraform project heavily relies on a state file. The Terraform state file is critical. Among others, the state file is responsible for:
- holding a representation of the actual infrastructure to calculate the execution plan (preview changes before applying them)
- controlling concurrent executions if being stored in a remote state backend such as Azure Storage Account
The state file is the basement for some of the most popular Terraform features. However, it is often the reason why teams have to troubleshoot or reconcile configuration drifts in Terraform. Terraform state may also contain sensitive data (things like passwords), so it must be protected.
Language Syntax
Bicep and Terraform have dedicated languages that developers use to express infrastructure as code. Both languages are declarative. They are fairly similar and share some grammar. Let’s highlight some of the differences, strengths, and weaknesses:
Custom validations
Although underlying ARM APIs will validate every property during deployment, providing custom validation in IaC projects is essential. Custom validation allows you to:
- enforce lightweight governance rules before any deployment hits Azure Resource Manager API
- validate user input against individual policies
As shown in the first snippet, custom validation in Bicep relies on a pattern called decorators
. Parameters are decorated with custom validation rules. Users can apply multiple decorators
to a single parameter if required:
// sample.bicep
@minLength(5)
@allowed([
'germanywestcentral'
'eastus2'
'westeurope'
])
param location string
In contrast, custom validation in Terraform feels like a small macro language. Validation rules in Terraform can leverage any of the built-in functions to depict the logic:
# sample.tf
variable "location" {
type = string
validation {
condition = contains(["eastus2", "germanywestcentral", "westeurope"], var.location)
error_message = "Please use one of the following regions (germanywestcentral|eastus2|westeurope)."
}
}
Looking at both - fairly simple - examples, I prefer Bicep’s custom validation syntax. It is easy to memorize and feels more focused. Concatenating different decorators
feels more natural for a declarative language.
Resource Version pinning
Azure services evolve. Continuous improvements may result in new ARM API versions being published. The azurerm
provider in Terraform relies on the most recent API version. That said, developers may want to use an old version of the provider when looking for a specific version for a particular Azure service. However, this may lead to using older API versions for other Azure resources too. As an effect, developers don’t have to specify the API version when describing a particular resource, as shown here:
# sample.tf
resource "azurerm_container_registry" "acr" {
name = "thorstenhans"
resource_group_name = azurerm_resource_group.rg.name
location = var.location
sku = "Standard"
}
In contrast, type identifiers in Bicep contain the version number when multiple versions are available. To deploy the same Azure Container Registry (ACR) in Bicep using the most recent API preview version, the following code is required:
// sample.bicep
resource acr 'Microsoft.ContainerRegistry/registries@2020-11-01-preview' = {
name: 'thorstenhans'
location: location
sku: {
name: 'Standard'
}
}
I would love to get rid of the version specification @2020-11-01-preview
. I would welcome some kind of pointer to the most recent version. For example something like Microsoft.ContainerRegistry/registries
or Microsoft.ContainerRegistry/registries@latest
pointing to the current version.
Conditional Deployments
Conditional deployments are often required in IaC. For example, users want to deploy assistance services only for staging or production environments but ignore them for the development environment to reduce costs or reuse existing services in development. Bicep and Terraform use slightly different syntax to support conditional resource deployments.
In Bicep, a conditional expression is added to the resource declaration using the if
keyword as shown here:
// sample.bicep
param is_dev bool
resource ai 'Microsoft.Insights/components@2020-02-02-preview' = if (!is_dev) {
name: 'ai-test'
kind: 'web'
location: 'westeurope'
}
In contrast, Terraform uses the count
property - which is available on every resource - to support conditional deployments:
# sample.tf
variable "is_dev" {
type = bool
}
resource "azurerm_application_insights" ai {
name = "thns-demo"
resource_group_name = "tf-sample"
location = "germanywestcentral"
application_type = "web"
count = var.is_dev? 0 : 1
}
Comparing both snippets, you will quickly realize that Bicep has an advantage here. Conditionals in Bicep look and feel cleaner compared to Terraform.
Tooling
Robust and efficient tooling is very important to many developers (including me). In this section, we will look at different tool aspects like:
- Command Line Interface (CLI) productivity
- Coding assistance and IDE integration
- Import existing ARM templates to head-start a project
Command Line Interfaces
Terraform CLI is pure dope. It runs almost everywhere, it is easy to install (just a binary), and it is super robust. Terraform CLI is a workhorse that has just one purpose: It assists you in getting your stuff done.
Some of the most prominent Terraform CLI commands are:
terraform init
- Initialize a Terraform project and installs all required providersterraform plan
- Preview Changes before modifying underlying cloud infrastructureterraform apply
- Apply current configurationterraform fmt
- Reformat all.tf
files in the current folderterraform validate
- Validate the Terraform project
Bicep integrates with Azure CLI, which is also amazing. I love az
and use it every day. Shipping bicep as an extension for Azure CLI was obvious and better than building it from scratch with - let’s say Node.js.The Bicep CLI offers just a handful of sub-commands. Maybe because Bicep is still relatively new, maybe because it is leveraging existing mechanisms and patterns established for ARM templates.
Most important az
commands in the context of Bicep are:
az bicep build
- Transpile one or more Bicep files into an ARM templateaz bicep decompile
- Decompile ARM template to Bicepaz bicep upgrade
- Update the Bicep CLIaz deployment group create
- Deploy on Azure Resource Group scopeaz deployment sub create
- Deploy on Azure Subscription scope
I would love to see a dedicated format
or fmt
sub-command for the Bicep CLI with corresponding flags like --check
. I use those commands provided by Terraform (terraform fmt --check
) in CI/CD to ensure proper styling.
Bicep Execution Plan (aka preview changes)
Previewing changes before modifying underlying cloud infrastructures is perhaps the most prominent USP of Terraform. Both, Azure CLI and Azure PowerShell offer similar functionalities using the what-if
commands as shown below:
# create a new Resource Group
az group create -n rg-test -l germanywestcentral
# compile Bicep to ARM Template
az bicep build -f myinfra.bicep
# preview potential changes
az deployment group what-if -g rg-test -f myinfra.json
Coding assistance and IDE integration
There are Visual Studio Code extensions for both languages. Both of them use an underlying language service to provide code completion and rich IntelliSense capabilities.
The (now) official Terraform extension for VSCode is okay-ish. Depending on the complexity of your infrastructure project, you may see IntelliSense break from time to time. Referencing resources
, variables
, and locals
will stop working at some point. At least that’s what I experience now and then in different environments.
Although Bicep is so new, their coding assistance already outperforms Terraform. Referencing existing elements, auto-completion, and validation works for me like a charm. At least as long as all infrastructure belongs to a single file.
Getting proper code completion and rich IntelliSense is mission-critical. Once people grasped the language basics, they always look for great tooling to improve their productivity. Visual Studio Code tooling is an area where Terraform could and should improve in the future.
Import existing ARM templates
Many teams have existing ARM templates to migrate or import into tools like Terraform or Bicep. Both tools provide a mechanism to import existing ARM templates. However, there are fundamental differences.
Bicep CLI comes with the handy decompile
command. It takes an existing ARM template (multiple JSON template files are supported) and tries to decompile it to .bicep
.
I’ve tested az bicep decompile
with a few (fairly simple) ARM templates, and it worked for me. On the other side, when I tried to decompile more complex infrastructures, I ran into some errors, and the resulting Bicep file was invalid. I bet that tooling will improve in this area over time. At least it is worth giving it a shot.
Terraform has a different approach regarding pre-existing ARM templates. Terraform treats an entire ARM template as an atomic resource. Although this seems handy in the first place, it lacks the possibility to do fine-granular modifications.
That said, there are several open-source tools available that try to address the translation of ARM templates into Terraform language (previously known as HashiCorp Configuration Language). However, I found those tools often rely on older versions of providers like azurerm
, resulting in incompatibilities.
Conclusion
Bicep language is awesome and its tooling is robust. Compared to Terraform, developers could gain more speed when it comes to parameters and their validation logic.
Obviously, Bicep is built to provision Azure infrastructures. In multi-cloud scenarios, Terraform is still the best option.
Speaking of dedicated Azure infrastructures, Bicep is the preferred solution due to the seamless integration with the underlying Azure Resource Manager API.
In the end, I would advise you to choose the tooling based on the actual requirements. For all my Azure-only stuff, I’ll head direction Bicep. However, plenty of our customers are happy with Terraform! Their projects keep growing and need some kind of maintenance from time to time. That said, there is enough space for Terraform and Bicep - at least in my toolbelt.