Using Bicep to Deploy an Azure Virtual Machine

Share on:

Welcome to another blog on Automation! This one will be focused on deploying resources in Azure using Bicep. Follow along to understand what Bicep is and my thoughts on it.

I always find it fun to play with new technologies. Most of my demos use Terraform or Ansible, but there are so many tools out there, that when I get asked for something new I go out and investigate it.

When I was first using Bicep, it seemed to me very familar to CloudFormation, which makes sense as it a similar way to deploy resources to a different Cloud Provider. The one thing I was NOT expecting, is that Bicep is only used to Deploy resources, when you delete a Bicep deployment–the resources still stay and require manual cleanup.

This blog will cover how to deploy an Azure VM using Bicep.

According to Microsoft Bicep is a Domain-specific Language (DSL) that is used to deploy resources using a declaritive syntax. It provides a new experience for users trying to use Azure Resource Manager (ARM) templates.

Lets dive into deploying a Azure VM with Bicep below!

Azure CLI

  • This is used to authenticate and deploy the resources to Azure.

Microsoft Azure Account

  • This is the infrastructure to run the Azure virtual machines.

My initial goal of building a Bicep file, was to use it to deploy a basic Azure VM. A Bicep file contains 3 main items.

  • Parameters
  • Variables
  • Resources

A resource block is used to deploy a particular resource, this could be a vnet, nsg, subnet, vm, extension, etc…. really any Azure resource can be deployed here. A variable will be used in the resource block to specify a particular item, and a parameter will be used to pass an inline string into the variable fields.

The below sample deploys a Windows VM with a New NSG and a New Public IP into an existing Resource Group, VNET and Subnet.

@description('The name of the VM')
param virtualMachineName string
@description('The virtual machine size.')
param virtualMachineSize string = 'Standard_D2s_v5'
@description('Specify the name of an existing VNet in the same resource group')
param existingVirtualNetworkName string
@description('Specify the resrouce group of the existing VNet')
param existingVnetResourceGroup string
@description('Specify the name of the Subnet Name')
param existingSubnetName string
@description('The Windows version for the VM. This will pick a fully patched image of this given Windows version.')
@allowed([
'2016-datacenter-gensecond'
'2016-datacenter-server-core-g2'
'2016-datacenter-server-core-smalldisk-g2'
'2016-datacenter-smalldisk-g2'
'2016-datacenter-with-containers-g2'
'2016-datacenter-zhcn-g2'
'2019-datacenter-core-g2'
'2019-datacenter-core-smalldisk-g2'
'2019-datacenter-core-with-containers-g2'
'2019-datacenter-core-with-containers-smalldisk-g2'
'2019-datacenter-gensecond'
'2019-datacenter-smalldisk-g2'
'2019-datacenter-with-containers-g2'
'2019-datacenter-with-containers-smalldisk-g2'
'2019-datacenter-zhcn-g2'
'2022-datacenter-azure-edition'
'2022-datacenter-azure-edition-core'
'2022-datacenter-azure-edition-core-smalldisk'
'2022-datacenter-azure-edition-smalldisk'
'2022-datacenter-core-g2'
'2022-datacenter-core-smalldisk-g2'
'2022-datacenter-g2'
'2022-datacenter-smalldisk-g2'
])
param OSVersion string = '2019-datacenter-smalldisk-g2'
@description('The admin user name of the VM')
param adminUsername string
@description('The admin password of the VM')
@secure()
param adminPassword string
@description('Location for all resources.')
param location string = resourceGroup().location
var networkInterfaceName = '${virtualMachineName}-nic'
var networkSecurityGroupName = '${virtualMachineName}-nsg'
var networkSecurityGroupRules = [
{
name: 'RDP'
properties: {
priority: 300
protocol: 'Tcp'
access: 'Allow'
direction: 'Inbound'
sourceAddressPrefix: '*'
sourcePortRange: '*'
destinationAddressPrefix: '*'
destinationPortRange: '3389'
}
}
]
var publicIpAddressName = '${virtualMachineName}-publicip-${uniqueString(virtualMachineName)}'
var publicIpAddressType = 'Dynamic'
var publicIpAddressSku = 'Basic'
var nsgId = networkSecurityGroup.id
var subnetRef = resourceId(existingVnetResourceGroup, 'Microsoft.Network/virtualNetWorks/subnets', existingVirtualNetworkName, existingSubnetName)
resource publicIpAddress 'Microsoft.Network/publicIPAddresses@2022-01-01' = {
name: publicIpAddressName
location: location
sku: {
name: publicIpAddressSku
}
properties: {
publicIPAllocationMethod: publicIpAddressType
}
}
resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2022-01-01' = {
name: networkSecurityGroupName
location: location
properties: {
securityRules: networkSecurityGroupRules
}
}
resource networkInterface 'Microsoft.Network/networkInterfaces@2022-01-01' = {
name: networkInterfaceName
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
subnet: {
id: subnetRef
}
privateIPAllocationMethod: 'Dynamic'
publicIPAddress: {
id: publicIpAddress.id
}
}
}
]
enableAcceleratedNetworking: true
networkSecurityGroup: {
id: nsgId
}
}
}
resource virtualMachine 'Microsoft.Compute/virtualMachines@2022-03-01' = {
name: virtualMachineName
location: location
properties: {
hardwareProfile: {
vmSize: virtualMachineSize
}
storageProfile: {
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'Premium_LRS'
}
}
imageReference: {
publisher: 'MicrosoftWindowsServer'
offer: 'WindowsServer'
sku: OSVersion
version: 'latest'
}
}
networkProfile: {
networkInterfaces: [
{
id: networkInterface.id
}
]
}
osProfile: {
computerName: virtualMachineName
adminUsername: adminUsername
adminPassword: adminPassword
windowsConfiguration: {
enableAutomaticUpdates: true
provisionVMAgent: true
}
}
}
}

Building the Bicep template was the hard part, it’s now easy to deploy the template through the Azure CLI. This document covers the deployment cli tasks in depth, but lets look below to see what we need to do specifically for this template.

Some of the variables were hard coded such as the Instance Type, however other variables are being passed through parameters. These are things that are dynamic such as the name, VNET, Subnet, Username and Password.

To begin the first thing we will need to do is login to Azure

1az login
shell

Once we log in we are able to run the command to execute the creation of the Bicep file.

1az deployment group create --resource-group 'MyVMResourceGroup' \
2--template-file deploy_win_vm.bicep \
3--parameters virtualMachineName='testvm' existingVirtualNetworkName='MyVNET' \
4existingVnetResourceGroup='MyVNET-rg' existingSubnetName='MySubnet' \
5adminUsername='david' adminPassword='abcd1234!'
shell

Once the deployment is running you can track it by navigating to the Resource Group and selecting Deployments. Here is where you can track the status of failed or success. When the deployment is completed, if successful you will see everything as green and can provide to the resource group to see what was created.

Hopefully this helped you understand what Bicep is and learn about it. I still really wish Bicep was able to manage the entire state of a deployment such as creation and deletion, but until then its still a really great tool! I will definitely be exploring this a bit more so look out for followup blog posts.

Any questions or comments? Leave them below.

See Also