So I had this conversation today, “What is Bicep?” And it’s a good question. Bicep is a language designed around “infrastructure-as-code”, and that means that at it’s core, bicep is used to standup infrastructure in the Microsoft Azure Cloud. So the next logically question, what do I mean by “infrastructure?”
When you stand up an application in the cloud, you need to stand up services that are responsible for things like compute, storage, networking at the minimum, but can also include things like secret management, configuration management, databases, etc.
What is bicep?
Now when it comes to deploying resources in Azure, you really have 3 options:
- Use the Azure Portal
- Use the Azure CLI
- Use Infrastructure-as-code (bicep, ARM Templates, terraform, etc)
Now there’s nothing wrong with using any of these, but they each have their own benefits / problems, and below is a summary of each:
Method | Pros | Cons |
---|---|---|
Azure Portal | Easy to use, UI driven, straight forward | Non repeatable, and manual effort, means better chance for errors |
Azure Command Line | Easy to use if you are used to scripting, straight forward to write a script | Can be written into a script file, but very deterministic and if something goes wrong, left in a broken state. |
Infrastructure-as-code | Straight forward to deploy, Repeatable and addresses config drift | Requires a little investment to code |
Now, I recommend anyone who has plans of deploying their work into a production environment start at the infrastructure-as-code level. Because it makes life significantly easier to deploy an application and iterate on it once you start with the code. Once you’ve built something, there are tools to generate bicep based on an exported template, but they usually aren’t as clean or easy to follow.
Why use bicep?
So as I mentioned above, there are lots of options with regard to infrastructure-as-code. One of the most prevalent options is Terraform. And don’t get me wrong, I love Terraform. It’s a great tool, and I know I will be doing posts about it in the future. But Terraform’s greatest strength is also it’s biggest weakness sometimes. Terraform is Open-Sourced, and Cloud Independent. And what that means is you can write Terraform to deploy to AWS, GCP, and Azure, and the code looks “almost” the same.
The downside to that, is that when any of those clouds make updates to their APIs, change their parameters, or even add new services. There is a time-gap between when it goes GA, and when Terraform supports it.
Bicep is built and maintained by Microsoft, which means that the second a new service GA’s, bicep supports it. Mainly because the team responsible for bicep is integrated with other engineering teams.
So then the question becomes for Azure, ARM Templates or Bicep. This is a bit of an ironic question. Mainly because bicep compiles down to ARM. The main reason bicep exists is that for a lot of people, ARM is difficult to read, and the lack of intellisence makes it almost workable. In fact the names matter, this language is called “Bicep” because a bicep muscle moves the arm.
How do I install bicep?
The good news is that bicep is a part of the azure cli, and while a separate module it is integrated. So the steps to install are the following:
- Install Azure CLI: First step is to install the Azure CLI.
- Install Bicep Tools: This article will provide details for installing bicep.
- Extension Marketplace: Here is a helpful extension for doing bicep work.
Let’s see code…what is bicep?
Let’s take a simple example of a bicep template, and create a storage account. I would argue this is the most simplistic resource in azure.
Below is an example of a bicep template for storage, put the content of “storage.bicep”:
param storageAccountName string
param location string = resourceGroup().location
param skuName string = 'Standard_LRS'
param kind string = 'StorageV2'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccountName
location: location
sku: {
name: skuName
}
kind: kind
properties: {
accessTier: 'Hot'
}
}
output storageAccountId string = storageAccount.id
output storageAccountName string = storageAccount.name
output storageAccountPrimaryLocation string = storageAccount.properties.primaryLocation
Now, if I want to deploy that file, I can do so with the following commands:
az cloud set --name AzureUSGovernment # If you are connecting to azure government
az login --use-device-code
Then the following command to create the resource group
az group create --name storage-test --location usgovvirginia
Then the following command to deploy the template:
az deployment group create --resource-group storage-test --template-file ./storage.bicep --parameters storageAccountName=kevinteststg
That really is the most basic deployment of bicep, but ultimately this is what it takes to deploy infrastructure-as-code.
How can I organize the code?
One of the most important questions I have one I look at a new coding technology, is how can I organize code in my solution. The answer is that bicep makes this really easy by referencing the file using the modules type:
If I add the following to a file “main.bicep”
module storage_a './storage.bicep' = {
name: 'storageA'
params: {
storageAccountName: 'storagea'
location: resourceGroup().location
}
}
module storage_b './storage.bicep' = {
name: 'storageB'
params: {
storageAccountName: 'storageb'
location: resourceGroup().location
}
}
And then I can deploy this code with the following:
az deployment group create --resource-group storage-test --template-file ./main.bicep
And the result of this will be two identically configured storage accounts.
Where bicep shows it’s power, is that bicep is “desired-state-configuration”, where the code identifies the desired state and updates the resources to match the desired state. For example, if I update my storage.bicep to the following:
param storageAccountName string
param location string = resourceGroup().location
param skuName string = 'Standard_LRS'
param kind string = 'StorageV2'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccountName
location: location
sku: {
name: skuName
}
kind: kind
properties: {
accessTier: 'Hot'
}
}
resource blobService 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = {
name: 'default'
parent: storageAccount
}
resource storageContainer 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = {
name: 'container-1'
parent: blobService
properties: {
publicAccess: 'None'
}
}
output storageAccountId string = storageAccount.id
output storageAccountName string = storageAccount.name
output storageAccountPrimaryLocation string = storageAccount.properties.primaryLocation
And if I run the same deployment command:
az deployment group create --resource-group storage-test --template-file ./main.bicep
NOTE: This is the same command as above.
Notice that after that is done, notice it didn’t create new storage accounts, it just added containers to the existing storage accounts:
az storage container list --account-name storagea --query "[].name" --output table
Output:
Result
-----------
container-1
How can I generate bicep based on something deployed?
Now I mentioned above, that if I have resources deployed and I want to create a bicep template, to do this, go to the resource group:
Step 1 – Go to the resource / resource group you want to export.

Step 2 – Then you can download the template:

Step 3 – From there you will get a zip file containing the files identified below:

From there, you can run the following command:
az bicep decompile -f ./template.json
The results are the following:
param storageAccounts_storagea_name string = 'storagea'
param storageAccounts_storageb_name string = 'storageb'
param storageAccounts_kevinteststg_name string = 'kevinteststg'
resource storageAccounts_kevinteststg_name_resource 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccounts_kevinteststg_name
location: 'usgovvirginia'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_0'
allowBlobPublicAccess: false
networkAcls: {
bypass: 'AzureServices'
virtualNetworkRules: []
ipRules: []
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
encryption: {
services: {
file: {
keyType: 'Account'
enabled: true
}
blob: {
keyType: 'Account'
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
accessTier: 'Hot'
}
}
resource storageAccounts_storagea_name_resource 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccounts_storagea_name
location: 'usgovvirginia'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_0'
allowBlobPublicAccess: false
networkAcls: {
bypass: 'AzureServices'
virtualNetworkRules: []
ipRules: []
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
encryption: {
services: {
file: {
keyType: 'Account'
enabled: true
}
blob: {
keyType: 'Account'
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
accessTier: 'Hot'
}
}
resource storageAccounts_storageb_name_resource 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccounts_storageb_name
location: 'usgovvirginia'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_0'
allowBlobPublicAccess: false
networkAcls: {
bypass: 'AzureServices'
virtualNetworkRules: []
ipRules: []
defaultAction: 'Allow'
}
supportsHttpsTrafficOnly: true
encryption: {
services: {
file: {
keyType: 'Account'
enabled: true
}
blob: {
keyType: 'Account'
enabled: true
}
}
keySource: 'Microsoft.Storage'
}
accessTier: 'Hot'
}
}
resource storageAccounts_kevinteststg_name_default 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = {
parent: storageAccounts_kevinteststg_name_resource
name: 'default'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
cors: {
corsRules: []
}
deleteRetentionPolicy: {
allowPermanentDelete: false
enabled: false
}
}
}
resource storageAccounts_storagea_name_default 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = {
parent: storageAccounts_storagea_name_resource
name: 'default'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
cors: {
corsRules: []
}
deleteRetentionPolicy: {
allowPermanentDelete: false
enabled: false
}
}
}
resource storageAccounts_storageb_name_default 'Microsoft.Storage/storageAccounts/blobServices@2023-05-01' = {
parent: storageAccounts_storageb_name_resource
name: 'default'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
cors: {
corsRules: []
}
deleteRetentionPolicy: {
allowPermanentDelete: false
enabled: false
}
}
}
resource Microsoft_Storage_storageAccounts_fileServices_storageAccounts_kevinteststg_name_default 'Microsoft.Storage/storageAccounts/fileServices@2023-05-01' = {
parent: storageAccounts_kevinteststg_name_resource
name: 'default'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
protocolSettings: {
smb: {}
}
cors: {
corsRules: []
}
shareDeleteRetentionPolicy: {
enabled: true
days: 7
}
}
}
resource Microsoft_Storage_storageAccounts_fileServices_storageAccounts_storagea_name_default 'Microsoft.Storage/storageAccounts/fileServices@2023-05-01' = {
parent: storageAccounts_storagea_name_resource
name: 'default'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
protocolSettings: {
smb: {}
}
cors: {
corsRules: []
}
shareDeleteRetentionPolicy: {
enabled: true
days: 7
}
}
}
resource Microsoft_Storage_storageAccounts_fileServices_storageAccounts_storageb_name_default 'Microsoft.Storage/storageAccounts/fileServices@2023-05-01' = {
parent: storageAccounts_storageb_name_resource
name: 'default'
sku: {
name: 'Standard_LRS'
tier: 'Standard'
}
properties: {
protocolSettings: {
smb: {}
}
cors: {
corsRules: []
}
shareDeleteRetentionPolicy: {
enabled: true
days: 7
}
}
}
resource Microsoft_Storage_storageAccounts_queueServices_storageAccounts_kevinteststg_name_default 'Microsoft.Storage/storageAccounts/queueServices@2023-05-01' = {
parent: storageAccounts_kevinteststg_name_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
resource Microsoft_Storage_storageAccounts_queueServices_storageAccounts_storagea_name_default 'Microsoft.Storage/storageAccounts/queueServices@2023-05-01' = {
parent: storageAccounts_storagea_name_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
resource Microsoft_Storage_storageAccounts_queueServices_storageAccounts_storageb_name_default 'Microsoft.Storage/storageAccounts/queueServices@2023-05-01' = {
parent: storageAccounts_storageb_name_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
resource Microsoft_Storage_storageAccounts_tableServices_storageAccounts_kevinteststg_name_default 'Microsoft.Storage/storageAccounts/tableServices@2023-05-01' = {
parent: storageAccounts_kevinteststg_name_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
resource Microsoft_Storage_storageAccounts_tableServices_storageAccounts_storagea_name_default 'Microsoft.Storage/storageAccounts/tableServices@2023-05-01' = {
parent: storageAccounts_storagea_name_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
resource Microsoft_Storage_storageAccounts_tableServices_storageAccounts_storageb_name_default 'Microsoft.Storage/storageAccounts/tableServices@2023-05-01' = {
parent: storageAccounts_storageb_name_resource
name: 'default'
properties: {
cors: {
corsRules: []
}
}
}
resource storageAccounts_storagea_name_default_container_1 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = {
parent: storageAccounts_storagea_name_default
name: 'container-1'
properties: {
immutableStorageWithVersioning: {
enabled: false
}
defaultEncryptionScope: '$account-encryption-key'
denyEncryptionScopeOverride: false
publicAccess: 'None'
}
dependsOn: [
storageAccounts_storagea_name_resource
]
}
resource storageAccounts_storageb_name_default_container_1 'Microsoft.Storage/storageAccounts/blobServices/containers@2023-05-01' = {
parent: storageAccounts_storageb_name_default
name: 'container-1'
properties: {
immutableStorageWithVersioning: {
enabled: false
}
defaultEncryptionScope: '$account-encryption-key'
denyEncryptionScopeOverride: false
publicAccess: 'None'
}
dependsOn: [
storageAccounts_storageb_name_resource
]
}
Notice that the above, is a valid bicep, but is not as clean as my above code. But it is valid and will deploy.
Where can I find more information on bicep?
Below are the tools that we discussed and some walkthroughs for setting up your environment:
And for more information on Bicep, you can find more details here:
Below is some training modules for microsoft:
Beginner:
- Introduction to Infrastructure-as-code-using bicep
- Build your first bicep template
- Funadmentals of bicep
Intermediate:
- Building reusable bicep templates by using parameters
- Create composable bicep templates using modules
- Structure your bicep code for collaboration
- Intermediate Bicep
- Deploy child and extension resources by using bicep
- Manage changes to your bicep code
- Review Azure Infrastructure changes using bicep
- Migrate azure resources and JSON ARM Templates
Here are some samples:
And here’s a great video: