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! 🚀