Create an Azure VM with Bicep: A Step-by-Step Guide.

Introduction

In this guide, we’ll walk through deploying an Azure Virtual Machine (VM) using Bicep, Microsoft’s domain-specific language (DSL) for Azure Resource Manager (ARM) templates.

Bicep provides a more readable and concise syntax compared to raw ARM JSON templates while retaining the same functionality. If you’re working with Infrastructure as Code (IaC) in Azure, Bicep is the recommended alternative to ARM.

We’ll cover how to create a fully functional Azure VM, including:
Resource Group creation
Virtual Network (VNet) and Subnet setup
Network Security Group (NSG) for security
Public and Private IP configuration
Network Interface (NIC) association
Deploying a Windows Server VM

By the end of this tutorial, you will have an automated and repeatable method to provision Azure Virtual Machines using Bicep and Azure CLI. 🚀


Prerequisites

Before getting started, ensure you have:
An active Azure subscription
Azure CLI installed (az --version)
Bicep CLI installed (az bicep install)
VS Code with the Bicep extension (for syntax highlighting & validation)


Step 1: Set Up Your Bicep File

Create a new file called main.bicep:

1touch main.bicep

Then, open it in Visual Studio Code for editing.


Step 2: Define the Azure Resource Group

All Azure resources should be placed inside a Resource Group. Let’s define one in Bicep:

 1param location string = 'northeurope'
 2param resourceGroupName string = 'bicep-vm-resource-group'
 3
 4targetScope = 'subscription'
 5
 6resource rg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
 7  name: 'bicep-vm-resource-group'
 8  location: location
 9  tags: {
10    environment: 'demo'
11    project: 'bicep-vm-tutorial'
12  }
13}

🔹 Key Points:

✔ targetScope = ‘subscription’ allows us to create a Resource Group at the subscription level. ✔ We define tags for easier resource tracking and cost management.


Step 3: Define the Virtual Network and Subnet

A Virtual Network (VNet) is required for our VM to communicate securely.

 1param location string
 2param resourceGroupName string
 3
 4resource vnet 'Microsoft.Network/virtualNetworks@2021-02-01' = {
 5  name: 'bicep-vnet'
 6  location: location
 7  resourceGroupName: resourceGroupName
 8  properties: {
 9    addressSpace: {
10      addressPrefixes: ['10.0.0.0/16']
11    }
12    subnets: [
13      {
14        name: 'frontendSubnet'
15        properties: {
16          addressPrefix: '10.0.1.0/24'
17          networkSecurityGroup: {
18            id: nsg.id
19          }
20        }
21      }
22    ]
23  }
24}
25
26resource nsg 'Microsoft.Network/networkSecurityGroups@2021-02-01' = {
27  name: 'vm-nsg'
28  location: location
29  properties: {
30    securityRules: [
31      {
32        name: 'allow-rdp'
33        properties: {
34          priority: 1000
35          access: 'Allow'
36          direction: 'Inbound'
37          protocol: 'Tcp'
38          sourcePortRange: '*'
39          destinationPortRange: '3389'
40          sourceAddressPrefix: '*'
41          destinationAddressPrefix: '*'
42          description: 'Allow RDP connections'
43        }
44      }
45    ]
46  }
47}
48
49output subnetId string = vnet.properties.subnets[0].id

🔹 Key Points:

✔ Network Security Group (NSG) is added for security. ✔ RDP (port 3389) is allowed, but in a real-world scenario, restrict source IPs. ✔ We define a subnet inside the VNet.


Step 4: Define a Public IP Address

 1param location string
 2
 3resource publicIP 'Microsoft.Network/publicIPAddresses@2021-02-01' = {
 4  name: 'bicep-public-ip'
 5  location: location
 6  properties: {
 7    publicIPAllocationMethod: 'Static'
 8    dnsSettings: {
 9      domainNameLabel: 'bicep-vm-${uniqueString(resourceGroup().id)}'
10    }
11  }
12  sku: {
13    name: 'Standard'
14  }
15}
16output publicIPId string = publicIP.id
17output fqdn string = publicIP.properties.dnsSettings.fqdn

🔹 Key Points:

✔ Static IP Allocation for better predictability. ✔ DNS Label automatically generates a user-friendly domain name.


Step 5: Define a Network Interface (NIC)

 1param location string
 2param subnetId string
 3param publicIPId string
 4
 5resource networkInterface 'Microsoft.Network/networkInterfaces@2021-02-01' = {
 6  name: 'bicep-vm-nic'
 7  location: location
 8  properties: {
 9    ipConfigurations: [
10      {
11        name: 'ipconfig1'
12        properties: {
13          subnet: {
14            id: subnetId
15          }
16          privateIPAllocationMethod: 'Dynamic'
17          publicIPAddress: {
18            id: publicIPId
19          }
20        }
21      }
22    ]
23  }
24}
25
26output nicId string = networkInterface.id

Step 6: Define the Virtual Machine

 1param location string
 2param nicId string
 3param adminUsername string
 4@secure()
 5param adminPassword string
 6
 7resource vm 'Microsoft.Compute/virtualMachines@2021-03-01' = {
 8  name: 'bicep-vm'
 9  location: location
10  properties: {
11    hardwareProfile: {
12      vmSize: 'Standard_B2s'
13    }
14    osProfile: {
15      computerName: 'bicep-vm'
16      adminUsername: adminUsername
17      adminPassword: adminPassword
18    }
19    storageProfile: {
20      imageReference: {
21        publisher: 'MicrosoftWindowsServer'
22        offer: 'WindowsServer'
23        sku: '2019-Datacenter'
24        version: 'latest'
25      }
26      osDisk: {
27        createOption: 'FromImage'
28        managedDisk: {
29          storageAccountType: 'Premium_LRS'
30        }
31      }
32    }
33    networkProfile: {
34      networkInterfaces: [{ id: nicId }]
35    }
36  }
37}
38
39output vmName string = vm.name

🔹 Key Points:

✔ Uses Windows Server 2019 as the OS. ✔ Secure Parameter (@secure()) for admin password. ✔ Uses Premium SSDs (Premium_LRS) for performance.


Step 7: Deploy the Bicep Templates

Login to Azure:

1az login

Deploy the main.bicep template:

1az deployment sub create   --location northeurope   --template-file main.bicep   --parameters adminPassword="YourSecurePassword"

To check deployment progress:

1az deployment sub list --query "[?name=='main'].properties.provisioningState" -o tsv

Conclusion

In this guide, we automated Azure Virtual Machine deployment using Bicep. This approach provides:

Improved Readability compared to raw ARM templates.
Modularity by breaking infrastructure into reusable Bicep modules.
Repeatability & Automation for consistent deployments.
Better Security with encrypted passwords and NSG rules.

Next Steps:

🔹 Integrate this with Azure DevOps or GitHub Actions for CI/CD.
🔹 Use Azure Key Vault for secure password storage.
🔹 Add monitoring using Azure Monitor & Log Analytics.


🔥 Have you tried Bicep? Drop your thoughts in the comments! 🚀