10 minute read

Microsoft has a native set of tools for IaC that can only be used for managing Azure resources, namely ARM templates and Bicep. This blog will use Terraform instead of these tools, but don’t let that deter you from using them instead of Terraform. Honestly, I like ARM templates and Bicep more, but Terraform will allow us to do some specific things that we can’t do with ARM templates and Bicep. Azure’s tools are completely free whereas Terraform may cost you some :money_with_wings: depending on how you implement it.

Please note that the examples in this post won’t really work and are more for a conceptual overview. I mean, they’ll work, but there’s a lot of important information missing that you’ll need to know before you can just start pounding out these commands on your keyboard. For a tutorial, go here.

ARM Templates

ARM templates allow you to declaratively define and deploy your Azure resources. They are simply JSON files that look like this:

"resources": [
  {
    "type": "Microsoft.Storage/storageAccounts",
    "apiVersion": "2022-09-01",
    "name": "mystorageaccount",
    "location": "centralus",
    "sku": {
      "name": "Standard_LRS"
    },
    "kind": "StorageV2"
  },
]

The example above is an ARM template that would create an Azure storage account in the Central US region. There are different ways to deploy ARM templates, but the most basic way is to use the command line. Let’s say we saved the above file as storage.json. If you used the Azure CLI, it would be something like this (assuming you already created the scrm-storage resource group):

az deployment group create --resource-group scrm-storage --template-file storage.json

That is a very simple example, but you will often need to get more complex. If you wanted to deploy two different storage accounts in the Central US region with different names, you could make a parameter for the name and specify the name when you deploy them. In our storage.json ARM template, we’re going to create a parameter named storageAccountName and reference it in the resource definition.

"parameters": {
  "storageAccountName": {
    "type": "string"
  }
},
"resources": [
  {
    "type": "Microsoft.Storage/storageAccounts",
    "apiVersion": "2022-09-01",
    "name": "[parameters('storageAccountName')]",
    "location": "centralus",
    "sku": {
      "name": "Standard_LRS"
    },
    "kind": "StorageV2"
  },
]

To deploy two storage accounts, you will need to run this twice, specifying the storage account name as a parameter each time.

az deployment group create --resource-group scrm-storage --template-file storage.json \
    --parameters storageAccountName='mystorageaccount'
az deployment group create --resource-group scrm-storage --template-file storage.json \
    --parameters storageAccountName='ohlookanotherstorageaccount'

If this all seems fun and exciting, welcome to the world of IaC! If you’re unimpressed, then let me assure you that this is a very rudimentary example and you can do way more stuff, such as:

  • Deploy multiple resources
  • Create parameter files to store different deployment configurations
  • Deploy different types of resources in the correct order (some of which may depend on each other)
  • Use variables and functions for more complex deployments, such as creating loops to deploy multiple NICs on a VM
  • Create ARM templates that can be called from other ARM templates

Bicep

Once you start writing ARM template files, you may notice that there’s a lot of JSON you have to wade through to get to the parts you’re interested in. We love JSON because it’s easier to read than its earlier cousins like XML. But there’s still a lot of brackets and squiggles that need to be opened and closed. Plus, once you start writing ARM templates for real, you’ll notice that your more complex functions may be hard to read. For example, look at this variable:

"variables" {
    "privateIPAddress": "[if(empty(parameters('ipAddress')), json('null'), parameters('ipAddress'))]"
}

Bicep is a different IaC language developed by Microsoft for deploying Azure resources. It basically shortens the JSON and makes it easier to read. For example, the above variable could be written like this in Bicep:

var privateIPAddress = empty(ipAddress) ? null : ipAddress

It’s more compact and easier to read, especially after you get a little more experience under your belt authoring your IaC files. Under the covers, Bicep is simply converting the Bicep files into ARM templates (aka, transpiling) and then deploying those. In fact, there are commands in Bicep to convert your Bicep files to ARM templates files if that’s what you want to do for some reason.

If we were to re-write our storage account ARM template example in Bicep, it would look like this:

param storageAccountName string

resource stg 'Microsoft.Storage/storageAccounts@2019-06-01' = {
  name: storageAccountName
  location: 'centralus'
  sku: {
    name: 'Standard_LRS'
  }
  kind: 'StorageV2'
}

Deploying a Bicep file is exactly the same as deploying an ARM template, except you generally use a different file extension for Bicep files. Let’s assume we’ll name this file storage.bicep. To deploy two different storage accounts using the above Bicep file, you’d run this:

az deployment group create --resource-group scrm-storage --template-file storage.bicep \
    --parameters storageAccountName='mystorageaccount'
az deployment group create --resource-group scrm-storage --template-file storage.bicep \
    --parameters storageAccountName='ohlookanotherstorageaccount'

If you’re starting out with ARM templates, I’d suggest just jumping straight to Bicep because it’s easier, but authoring ARM templates is its own type of fun depending on how your little brain works.

Storing Your ARM Templates or Bicep Files

You have several choices of where to store your ARM templates or Bicep files. Your approach will mostly depend on how you decide to deploy your infrastructure and if/how you want to create linked templates (ARM templates) or modules (Bicep).

Linked templates and modules are the same concept with different names depending on whether you’re talking about ARM templates or Bicep, but I’ll use the term “module” for both. When you start writing IaC, you’ll find that you are frequently deploying the same type of resource over and over again. For example, if you work at a software development company, you may find that you’re always deploying a Key Vault service every time you deploy an application. You may find that you have a specific way you like to deploy that Key Vault and you find yourself copying and pasting the resource into the IaC file each time. To streamline this, you can define the Key Vault in a module and then call that module from the ARM template or Bicep file. The tricky thing is that you call the module by specifying a URL, which usually means that URL needs to be public.

Here are your main options for storing your IaC files.

Storage Location Description
Git repository You can store your IaC files in a Git repository, such as GitHub. If you are calling modules, those modules need to be from a public repository which rightfully isn’t going to sit well with your security team.
Template specs
(ARM templates) (Bicep)
Template specs are a way to store your IaC files in Azure itself. You can then apply permissions to the template spec so that only certain users or services can access it. This is a great solution for storing your modules.
Private module registry Private registries are another way to store your Bicep modules in Azure. This only applies to Bicep modules, not ARM linked templates.
Deployment stacks This is another way to store your IaC files in Azure but with a focus on deploying groups of resources together. This is a great solution for deploying a bunch of resources that will live in the same resource group. When you update the IaC in a deployment stack, the changes get pushed out to all resources managed by that deployment stack. You can also prevent resources that are managed by a deployment stack from being manually changed or deleted.

The easiest method is to store your IaC in source control, but if you’re authoring modules this can get tricky. If you are writing modules, you may want to consider publishing your modules to Azure in a private module registry or as template specs and use Git for the main Bicep or ARM template files. When you start feeling comfortable using all this, you can move some or all of your IaC to deployment stacks.

Deployment

So far, I’ve mostly been talking about authoring your IaC files with some quick examples of deploying them using the Azure CLI. But deploying your IaC files so that the changes actually take effect is another story and there’s some things to know.

Deployment Modes

When deploying ARM templates or Bicep files, there are two deployment modes: incremental and complete. When you’re starting out, you don’t really need to worry about this, but using the wrong mode can really cause problems.

Incremental mode is the default mode and tells Azure to only make the specific changes that you’ve defined. So if you’re deploying a VM to a resource group and that resource group already has two storage accounts in it, incremental mode will only add a VM (or modify the existing VM).

Complete mode says “make my resource group look exactly like what I’ve specified in my IaC file.” In other words, if you’re Bicep file only has a VM and you deploy to a resource group that already has two storage accounts in it, well sir, those storage accounts will be removed. You didn’t define those storage accounts, so you’re saying that you don’t want them in that resource group. You have to affirmatively specify complete mode when deploying, so don’t do it unless you are sure that’s what you want.

IaC for VMresource incrementalmode VM updatedonly completemode VM updated andother resources removed
Complete mode will remove any resources from the resource group that you haven’t defined!

Manual Deployment

You’ve seen some examples of deploying using the Azure CLI, but you can also use other tools like PowerShell, the Azure Cloud Shell, or directly from your VS Code editor. I personally prefer PowerShell, but my examples for this blog are in the Azure CLI since it’s a tad more platform-neutral.

I consider these all examples of manual deployments because it requires a human to go type something. Whenever you’re typing commands, there’s a chance for something to go wrong. Plus you don’t have a simple way to determine when, why, and by whom an IaC file was deployed. The next subsection will talk about an automated approach.

However, one nice feature you have with the manual deployment is the What-if check. When you deploy your IaC with the what-if option, you’ll get a nice little report about what will be added, changed, and importantly, deleted. You should take heavy advantage of this when you are starting out (but really, all the time). Here are links to the ARM template and Bicep What-if docs, but they are both basically the same.

what-if output
See what chaos you’re about wreak with the what-if command.

CI/CD Pipelines

Until the release of deployment stacks, Azure didn’t have a solution for the automated deployment of IaC files. Other IaC products, like Terraform, have a way to properly manage your IaC with reporting tools, like a history of deployments.

Even if you don’t go the deployment stacks route, you can use a CI/CD pipeline to run your IaC code on a scheduled or event-driven way. There are a lot of events that can kick off a pipeline, but a very common one is to start a pipeline after your code is merged into the main branch. Microsoft Learn has documentation for deploying your IaC files with GitHub Actions or Azure DevOps CI/CD pipelines.

IaC changesmerged intoGit repository merge kicks offCI/CD pipeline pipeline deploysresources to Azure
ARM/Bicep can be deployed with CI/CD pipelines similar to Terraform Cloud Workspaces.
Git logo by Jason Long, licensed under Creative Commons Attribution 3.0 Unported

Using a CI/CD pipeline is particularly useful if you are a software company deploying your custom software. You can have a step early in your pipeline to deploy the infrastructure and then deploy your application. You can run the IaC step each time you re-run your pipeline since your IaC should be idempotent.

Other IaC Tools

ARM templates and Bicep are the main IaC tools, but there’s a couple of other ones worth mentioning.

The first one worth mentioning is Azure Blueprints. And by “worth mentioning,” I mean that it’s not worth mentioning at all because Blueprints will be deprecated in 2026. But you’ll still come across it from time to time. Blueprints was (is?) a way to make, well, blueprints of your Azure environment and then deploy them. It was a good idea but all the other tools have an edge on Blueprints, so it’s being squashed like a feeble bug by Microsoft.

The second one is Azure Policy. Azure Policy lets you enforce settings in your cloud environment. I don’t think it’s officially classified as an IaC tool, but it more or less serves that function, so let’s throw it in the IaC pile.

Updated:

Comments