Post

Using Bicep for Infrastructure as Code (IaC) on Azure

Bicep is Azure's streamlined approach to IaC, offering an intuitive syntax to simplify complex ARM-templates.

What is Bicep?

Azure Bicep is a Domain Specific Language. Yes, it is a language, comparable to Terraform language that tells Terraform how to manage a given collection of infrastructure. Bicep works as an abstraction layer on top of the ARM templates which simplifies the overall development experience with a very simple, clear syntax and you can build the code in line with the principles of modularity and reuse.

Why Bicep?

Unlike ARM, Bicep is easy to understand and code. Unlike the complication of JSON, Bicep is easy to write and follow. For instance, declaring parameters using Bicep vs JSON:

1
param location string = 'westeurope'
1
2
3
4
5
6
"parameters": {
  "location": {
    "type": "string",
    "defaultValue": "westeurope"
  }
}

Bicep modules help in breaking down the complexity of your infrastructure code. A Bicep module is a set of one or more resources to be deployed together. Modules are also a way to make Bicep code even more reusable. You can have a single Bicep module that lots of Bicep templates use. You can centralize the Bicep modules as per the standards and multiple business units can use the same modules in their templates to deploy the components.

The use of parameters and variables makes it very easy to make your Bicep templates or modules reusable and generic.

Bicep templates integrate with Azure DevOps and GitHub. You can easily write a YAML template and integrate your Bicep template as a task.

If you already have a setup using ARM templates, you can still decompile the ARM templates into Bicep templates and switch to Azure Bicep.

Templates and modules

Templates

1
2
3
4
<Root directory>
└── workloads
    └── main.bicep
    └── main.bicepparam
  • Each workload should be defined in its own directory. The directory is the logical name of the workload.
  • Each workload supports a single main.bicep template and should be located in the workload root directory.
  • Each deployment of the workload can be defined by a single parameter file with one of the following names:
    • environment.main.bicepparam
    • environment.bicepparam

By default, the target scope in a main.bicep is set to resourceGroup, but allowed values are:

  • resourceGroup - default value, used for resource group deployments.
  • subscription - used for subscription deployments.
  • managementGroup - used for management group deployments.
  • tenant - used for tenant deployments.

Definition (main.bicep)

1
2
3
4
5
6
7
8
targetScope = '<scope>'

@<description>
param <parameter-name> <parameter-data-type>

resource <resource-symbolic-name> '<resource-type>@<api-version>' = {
  <resource-properties>
}

Example (main.bicep)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
targetScope = 'resourceGroup'

param location string = resourceGroup().location
param storageAccountName string = 'toylaunch${uniqueString(resourceGroup().id)}'

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
  properties: {
    accessTier: 'Hot'
  }
}

Modules

Bicep modules enable you to reuse code from a Bicep file in other Bicep files. In the module declaration, you link to the file to reuse. When you deploy the Bicep file, the resources in the module are also deployed.

Use publicly available modules

Azure Verified Modules (AVM) is an initiative to consolidate and set the standards for what a good Infrastructure-as-Code module looks like. Modules will then align to these standards, across languages (Bicep, Terraform etc.) and will then be classified as AVMs and available from their respective language specific registries.

Use modules from a container registry in your organization

Modules can be written and made available by your CCoE team and consumed by your product teams throughout the organization.

Create your own modules

Definition (main.bicep)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
targetScope = '<scope>'

@<description>
param <parameter-name> <parameter-data-type>

resource <resource-symbolic-name> '<resource-type>@<api-version>' = {
  <resource-properties>
}

module <module-symbolic-name> '<path-to-file>' = {
  name: '<deployment-name>'
  params: {
    <parameter-names-and-values>
  }
}

Example (main.bicep)

1
2
3
4
5
6
7
8
9
10
11
12
13
targetScope = 'subscription'

param location string
param keyvaultName string

module keyvault '.bicep/keyvault/keyvault.bicep' = {
  name: 'keyvault-module'
  scope: resourceGroup(resourceGroupName)
  params: {
    keyvaultName: keyvaultName
    location: location
  }
}

Example (keyvault.bicep)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
targetScope = 'resourceGroup'

@description('Location of the resources.')
param location string

@description('Name of the Key Vault.')
param keyvaultName string

resource keyVault 'Microsoft.KeyVault/vaults@2023-02-01' = {
  name: keyvaultName
  location: location
  properties: {
    sku: {
      family: 'A'
      name: 'standard'
    }
  }
}

Summary

This guide explores Bicep’s advantages over ARM templates, demonstrating its simplified syntax and modular structure, which reduces complexity in Azure infrastructure code. With Bicep, users can leverage modules for code reusability and implement environment-specific parameters for flexibility. The Azure Verified Modules (AVM) initiative further supports code quality by providing standardized, pre-verified Bicep modules. Practical examples outline how to define templates and modules, linking reusable modules to main templates and setting deployment scopes effectively for resource groups, subscriptions, or management groups.

This post is licensed under CC BY 4.0 by the author.