Contents

Azure Bicep: An introduction on how to get started

Getting started with Azure Bicep


In this post I will explain what Bicep is and how you can get start using it for deploying resources to Microsoft Azure.

Azure Bicep: An introduction on how to get started

In this post I will take you through deploying resources to Azure using templates. We will be creating these templates using Azure Bicep.

jeroensAzureBicepARM.gif



📋 Prerequisites


[✔] Azure CLI

The first thing we will need is the Azure command-line interface (Azure CLI). This is a set of commands that can interact with Azure and its resources.

You can get the Azure CLI through the winget tool.

1
winget install Microsoft.AzureCLI

Or any of the other methods mentioned in the documentation: Install the Azure CLI.

Once succesfully installed you can simply run az in your terminal to see a list of all the base commands.

[✔] The Bicep Lang extension

Now that we have the Azure CLI installed we are going to install the Bicep extension.
You may have already seen it pop up when you ran the az command.
Simply run the following command to install the Bicep CLI:

1
az bicep install

If you already had the extension installed this may have been because you already ran an Azure CLI command that depends on the Bicep CLI.
The Bicep CLI will automatically get installed if this is the case. You can check your Bicep version by running:

1
az bicep version

and if it is not the latest then upgrade by running:

1
az bicep upgrade

if you have never touched the CLI before then checkout some of the commands by including -h / –help at the end.

[✔] VS Code Bicep Extension

I highly suggest using a code editor. My editor of choice is Visual Studio Code. Especially since it has the Bicep extension.

To install VS Code run:

1
winget install Microsoft.VisualStudioCode

or for a bunch of other methods visit VS Code setup overview.

Once you have Visual Studio Code installed. Open VS Code and go to the extensions tab. Search for “bicep” and select install.

You can verify whether the extension installed succesfully by creating a file with the .bicep extension. Save it and then check whether it picks up any of the IntelliSense features.

[✔] PowerShell AZ module

I am used to working with PowerShell so in this post I will be using the pwsh az module.
This is optional. If you want to follow along then run the below commands in a PowerShell terminal.

1
2
3
4
5
# PowerShell script execution policy must be set to remote signed or less restrictive
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser

# Works cross-platform - PowerShell 7.x and later is recommended
Install-Module -Name Az -Scope CurrentUser -Repository PSGallery -Force

Another option is using the Azure CLI we installed earlier. 👍

[✔] An Azure Subscription (Optional)

And of course if we want to deploy to Azure we are going to need an Azure Account with an active Subscription on which we have permissions so that we can deploy resources.
If you are new to Azure you can create an Azure free account. This will give you access to free tier services for 12 months and a $200 budget which you can spend within 30 days from signing up.
Noticed that I put within 30 days in bold? Don’t sign up late at night and think I will look at it tomorrow.
Nah dude, we don’t have time to lose! Sign up for this at a time that you know you have some time to spend.
You can start free here on this site..

And if you are a student you can try Azure without having to sign up with a credit card. Checkout the students page.

In case you can’t signup for any of the offers above and don’t have access to an active subscription you can checkout the Microsoft Learn site. You are limited with what you can do depending on what course you follow but that is not a problem. They have a wide variety of courses available (especially when it comes to Azure) and more and more of their courses come with a Sandbox environment. These Sanbox environment let you perform tasks in Azure (based on the course description) without needing an Azure account. And best of all no charging at all. Completely free! Downside is of course that you can’t save any of your work but I mean for learning purposes it is wise to delete all your deployed resources at the end of your learning sessions anyway. Unless you like to be a big spender. 💰

Go over the options I have just listed and make your decision. Once you are all set we can go on our adventure. You can also just do the reading thing. That is cool as well!

👨‍🏫 Azure Resource Manager


/2021/08/azure-bicep-intro/azureDummy-arm.webp

Whenever you want to deploy resources to Azure you are going to interact with the Azure Resource Manager (ARM). Whether you are using the Azure Portal, Azure Powershell, the Azure CLI or the Azure REST APIs. As you can imagine it is important for the ARM service to be available at all times. It is designed to be resilient and continuously available. Think:

  • Distributed services across regions
  • No dependencies on a single data center
  • No downtime during maintenace
  • et cetera.

No need for us to worry about all of that. We want to deploy and they will let us.

/2021/08/azure-bicep-intro/azureDummy-whiteboard.webp

How are we deploying things? Behind the scenes everything in Azure is stored in JSON format.

JSON format
JSON is a lightweight data-interchange format that is self describing and easy to read and understand. The website json.org shows examples messages formatted using JSON (and a comparison with the same text as XML).

You can verify this within the Azure portal. The portal is accessible through portal.azure.com.
When you have deployed a resource you can access that resource and export the template.

For example let’s say we create a Resource Group through the portal:

PortalRG.PNG

Once you click on the button Review + create you will get a last overview of the resource you are about to deploy and at the bottom right you will get an option to download the template.

If we press download we get the deployment template and we can also choose whether or not we want a parameter template with the values we have supplied earlier. In our case we get the following files:

Template.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.1",
    "parameters": {
        "rgName": {
            "type": "string"
        },
        "rgLocation": {
            "type": "string"
        },
        "tags": {
            "type": "object",
            "defaultValue": {}
        }
    },
    "variables": {},
    "resources": [
        {
            "type": "Microsoft.Resources/resourceGroups",
            "apiVersion": "2018-05-01",
            "location": "[parameters('rgLocation')]",
            "name": "[parameters('rgName')]",
            "properties": {},
            "tags": "[parameters('tags')]"
        }
    ],
    "outputs": {}
}

along with its parameter file. parameters.json:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
{
    "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "rgName": {
            "value": "new-resourcegroup"
        },
        "rgLocation": {
            "value": "westeurope"
        },
        "tags": {
            "value": {}
        }
    }
}

See, easy to understand. Right? :-)

Or you could checkout the Azure Resource Explorer on resources.azure.com. This is currently in preview and allows you to view the API and make API calls directly into your own subscriptions.
Because all of the earlier mentioned tools (portal, cli, etc) make requests through the same APIs you will see consistent results. All requests are handled using JSON. To showcase our Resource Group example of earlier we could simply run:

resourcesRG.PNG

So if we want to create the same resource group as earlier we would have to provide values for the Resource Name, the resource id, the name and its location.

But if all requests are in JSON format anyways then wouldn’t it be a good idea to deploy these templates directly? Yup, good question. That brings us to a concept called “Infrastructure as Code” and ARM templates.

❓ What are ARM Templates?


As we have seen earlier with our Resource Group deployment we can deploy resources to Azure using a template that is written in JSON format. Such a template is called an Azure Resource Manager template or ARM template in short.

/2021/08/azure-bicep-intro/azureDummy-think.webp

A template is declarative that means that in your ARM template you describe what you want. How to get to that state and what operations need to be done? Well, we don’t care. Azure will take care of all of that. Take into account that we provide the correct resources and their properties. And even if we didn’t the Azure Resource Manager has a built-in validation that will check the template before doing the actual deployment. If there is something wrong with the resource that we are trying to deploy then it will tell us and we are not stuck with only half of the deployment.

{ } The ARM Template syntax


A basic template file contains the sections: Parameters, Variables, Resources and Outputs. You can also use functions in your ARM template. Checkout the documentation for a full list of functions.

/2021/08/azure-bicep-intro/azureDummy-curlybraces.webp

The sections mentioned in ARM template format will look like this:

1
2
3
4
5
6
7
8
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {},
  "variables": {},
  "resources": [],
  "outputs": {}
}

We could make it even more minimalistic because the only keys that are required are the schema, content version and resources. We could remove the others however they have their use case so it is best to utilize them. The parameters section is used for input. That way we don’t have to hardcode each and every property and it gives us a way to reuse our templates. Handy! You can define variables using complicated expressions. You can for instance have a parameter that allows “QA” or “PRD” as input and based on the supplied value define a variable that will use a certain tier of a resource. Perhaps a Standard tier when deploying to QA and a Premium tier when that same resource gets deployed onto Production. And last but not least the outputs section. This is pretty straight forward. If you want to return any values from your deployed you can specify that.

In practice a template we can deploy will look something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
      "webAppName": {
        "type": "string",
        "defaultValue": "[concat('webApp-', uniqueString(resourceGroup().id))]",
        "minLength": 2,
        "metadata": {
          "description": "Unique name of the app to create or update."
        }
      },
      "location": {
        "type": "string",
        "defaultValue": "[resourceGroup().location]",
        "metadata": {
          "description": "Location of the resources. Default is RG location."
        }
      },
      "sku": {
        "type": "string",
        "defaultValue": "F1",
        "allowedValues": [
            "B1",
            "B2",
            "B3",
            "F1"
        ],
        "metadata": {
          "description": "The SKU of App Service Plan."
        }
      },
      "linuxFxVersion": {
        "type": "string",
        "defaultValue": "DOTNETCORE|3.0",
        "metadata": {
          "description": "The Runtime stack of current web app"
        }
      }
    },
    "variables": {
      "appServicePlanPortalName": "[concat('AppServicePlan-', parameters('webAppName'))]"
    },
    "resources": [
      {
        "name": "[variables('appServicePlanPortalName')]",
        "type": "Microsoft.Web/serverfarms",
        "apiVersion": "2020-06-01",
        "location": "[parameters('location')]",
        "sku": {
          "name": "[parameters('sku')]"
        },
        "kind": "linux",
        "properties": {
          "reserved": true
        }
      },
      {
        "name": "[parameters('webAppName')]",
        "type": "Microsoft.Web/sites",
        "apiVersion": "2020-06-01",
        "location": "[parameters('location')]",  
        "dependsOn": [
          "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanPortalName'))]"
        ],
        "properties": {
          "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanPortalName'))]",
          "siteConfig": {
            "linuxFxVersion": "[parameters('linuxFxVersion')]"
          }
        }
      }
    ]
  }

As you can see in the resources section we have specified two resources. A resource of the type Microsoft.Web/serverfarms and a resource of the type Microsoft.Web/sites. Please not that in the second resource we do have to specify a dependsOn key with the resource id of the serverfarm. This is because we need to have an app service plan in place before we can deploy a web app so it is key that one gets deployed before the other.

So far things have been going great. We can easily specify what we want to deploy and it is easy to read what we are deploying. The things is though that once you deploy more and more resources using the same template. Your template will get bigger and bigger. That’s a hell of a lot of curly braces! And the whole easy to read thing will not be so easy anymore and may even get confusing once you add in a bunch of depends on keys. Writing a template like this doesn’t feel natural either. Most of the time you have to copy & paste stuff in. A more programmary way of doing this would be great and let me tell you.. there is! 😀



💪 Project Bicep


Your expectations must be pretty high right now and that is good because they should be. Throws a front double bicep.

/2021/08/azure-bicep-intro/azureDummy-doublebicep.webp

A default resource in Bicep will look like the following. If you want to see this for yourself then simply open a file with the .bicep file extension in VS Code. If you type res a dropdown will appear which a bunch of snippets. If you select resource with defaults you will get the below code.

1
2
3
4
5
6
resource Identifier 'Provider/Type@Version' = {
  name: 'name'
  location: 'location'
  properties: {    
  }
}

Let’s break this down for a bit. We have the resource keyword which we also saw in our ARM template. Nothing new there, it is known. The Identifier keyword we haven’t seen before. If you have seen some bicep tmeplates before then you might have seen being mentioned as a symbolicName. This is not the name of the resource. That wouldn’t make sense either since we already have a name property on the next line. Instead the Identifier is the name that we can give our resource so that we can reference it by that name throughout our template. Really useful stuff! And then last but not least we have the Provider, Type and Version. We had those in our ARM templates as well but instead having them each as separate properties in our template we just specify them in the resource declaration. It’s a lot clearer this way in my opinion.

I know what you always wanted to have is an Azure Container Registry. I can’t give you that. Here, have it’s bicep template instead. Here ya go bud!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
resource acr 'Microsoft.ContainerRegistry/registries@2019-12-01-preview' = {
  name: 'nicelookingacrbro'
  location: 'West Europe'
  sku: {
    name: 'Basic'
  }
  properties: {
    adminUserEnabled: true
  }
}

🎈 More sections

You might be thinking now “where are the rest of our sections. I don’t see any curly braces.”.

Yup, time to add some. Parameters first that is.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
@minLength(5)
@maxLength(30)
@description('The name of the container registry')
param acrName string = 'acrgiveanyname'

@description('The value that indicates whether the admin user is enabled.')
param acrAdminUserEnabled bool = false

@description('The location of the resource. This cannot be changed after the resource is created.')
param location string = resourceGroup().location

@allowed([
  'Basic'
  'Standard'
  'Premium'
])
@description('The SKU of the container registry.')
param acrSku string = 'Basic'

// azure container registry
resource acr 'Microsoft.ContainerRegistry/registries@2019-12-01-preview' = {
  name: acrName
  location: location
  sku: {
    name: acrSku
  }
  properties: {
    adminUserEnabled: acrAdminUserEnabled
  }
}

You may have noticed two things. We have added parameters above our resource declaration and we have referenced the parameters by their name.

To put this into comparison with ARM the acrSku parameter would be defined as:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
"acrSku": {
  "type": "string",
  "defaultValue": "Basic",
  "metadata": {
    "description": "Tier of your Azure Container Registry."
  },
  "allowedValues": [
    "Basic",
    "Standard",
    "Premium"
  ]
}

and we would have to reference it in our resource like:

1
2
3
"sku": {
        "name": "[parameters('acrSku')]"
      },

Bicep is cleaner. I think there is not much else to parameters.

I do want to touch on the symbolicName part mentioned earlier though. We’ll check that out by adding an output section to our template. I thought of displaying the whole code here however we only need to add one line to our previous Bicep template. I don’t think that is worth it so I will just mention that part but imagine it being part of the same template.

When we create an Azure Container Registry we will have to give it a name. So let’s say we create an ACR with the name theirontemple and we want to push an image to that registry. In order to push an image to our newly created registry we need to know the value of the login server, which is a fuly qualified name ending with azurecr.io. So instead of looking this up after deployment we want our deployment to output this Login server name once we deployed our ACR. Our output will be:

1
output acrLoginServer string = acr.properties.loginServer

We are saying give me the output value for the login Server which in our case will be theirontemple.azurecr.io and store it in acrLoginServer. Notice the acr? That is the symbolicName we gave our resource. If you have VS Code open then remove the properties.loginServer and you will see all the outputs properties that you could specify.

Now that we have covered a few of the basics of an Bicep template. Let’s see what that “big” ARM template that we used earlier looks like in Bicep.

🛠 Decompile / Build

It is fast and easy to write it all in Bicep from scratch but why would we do that when we have the az bicep decompile function available to us? If I run this against our earlier template:

1
az bicep decompile -f .\deploy.json

We get the following message:

decompile warning
WARNING: Decompilation is a best-effort process, as there is no guaranteed mapping from ARM JSON to Bicep. You may need to fix warnings and errors in the generated bicep file(s), or decompilation may fail entirely if an accurate conversion is not possible. If you would like to report any issues or inaccurate conversions, please see https://github.com/Azure/bicep/issues.

Ours is looking great though (the template went from 74 lines of code to 43!):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@description('Unique name of the app to create or update.')
@minLength(2)
param webAppName string = 'webApp-${uniqueString(resourceGroup().id)}'

@description('Location of the resources. Default is RG location.')
param location string = resourceGroup().location

@description('The SKU of App Service Plan.')
@allowed([
  'B1'
  'B2'
  'B3'
  'F1'
])
param sku string = 'F1'

@description('The Runtime stack of current web app')
param linuxFxVersion string = 'DOTNETCORE|3.0'

var appServicePlanPortalName_var = 'AppServicePlan-${webAppName}'

resource appServicePlanPortalName 'Microsoft.Web/serverfarms@2020-06-01' = {
  name: appServicePlanPortalName_var
  location: location
  sku: {
    name: sku
  }
  kind: 'linux'
  properties: {
    reserved: true
  }
}

resource webAppName_resource 'Microsoft.Web/sites@2020-06-01' = {
  name: webAppName
  location: location
  properties: {
    serverFarmId: appServicePlanPortalName.id
    siteConfig: {
      linuxFxVersion: linuxFxVersion
    }
  }
}
dependsOn
Notice that this time we do not have to specify a dependsOn property. That is because we are referencing the symbolic name of the app service plan. See the value of the serverFarmId property in the web app resource. appServicePlanPortalName.id.

This works both ways. We can also write our code in Bicep first and decide to run az bicep build --file {bicep_file.bicep}. If we run that then it will spit out the ARM template for us. We could also just leave it as a bicep file and deploy the bicep file directly to Azure. For instance by running:

1
New-AzResourceGroupDeployment -ResourceGroup "yadayada" -TemplateFile "main.bicep" -...



💡 Conclusion


In this post we have learned a bit about what the Azure Resource Manager is, how we can deploy resources to Azure, What an ARM template is and of course Azure Bicep.

I also wanted to touch on using modules in Bicep however I’m still playing around with these myself. I might edit this post later to include a section about this as well. They are great. If you throw a bunch of dummy workout plan files in there then whenever your SO/Friends/Family accidently stumble upon your project they will think you are finally serious about working out instead of it being another one of those side projects of yours.. 😉

That was it. I hope you enjoyed it and have learned something.

/2021/08/azure-bicep-intro/azureDummy-point.webp

Now it is time for you to roll up your sleeves 💪

If you want to learn more about Bicep I suggest the following resources:

Have a very nice day! 👍



Btw, I discovered GIMP. It’s great 😉