Getting Started with Bicep and Infrastructure-as-code

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:

  1. Use the Azure Portal
  2. Use the Azure CLI
  3. 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:

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.

Bicep-1-Export-Template

Step 2 – Then you can download the template:

Bicep-2-Download-Template

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

Bicep-3-Template-Contents

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:

Intermediate:

Here are some samples:

And here’s a great video:

Leave a Reply

Your email address will not be published. Required fields are marked *