Azure: deploy a Linux VM with Bicep
Deploying virtual machines consistently and idempotently in Azure is a fundamental skill for any infrastructure professional. While the Azure portal…
Deploying virtual machines consistently and idempotently in Azure is a fundamental skill for any infrastructure professional. While the Azure portal offers a graphical interface, Infrastructure as Code (IaC) solutions like Bicep provide superior control, versioning, and repeatability. This article details the process of defining and deploying an Azure Linux VM using Bicep, covering core components, networking, storage, and secure access.
We will use Azure CLI for deployment, assuming you have it installed and configured with appropriate permissions. The Bicep version used for this guide is 0.23.1, but the core concepts remain applicable across recent versions.
Understanding the Bicep Structure for a VM
A Bicep file for a complete VM deployment typically involves several interdependent resources. At a minimum, you'll need the Virtual Machine itself, a Network Interface (NIC), a Virtual Network (VNet), and a Subnet. For secure access, an SSH public key is essential.
Resource Dependencies and Order
Bicep automatically infers dependencies between resources. For instance, a NIC depends on a Subnet, and a VM depends on a NIC. You generally declare resources in an order that respects these dependencies, though Bicep's orchestrator is quite intelligent about it.
resource vnet 'Microsoft.Network/virtualNetworks@2023-09-01' = {
name: 'vnet-prod-eastus-001'
location: resourceGroup().location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'subnet-app-001'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
]
}
}
resource publicIp 'Microsoft.Network/publicIPAddresses@2023-09-01' = {
name: 'pip-web-01'
location: resourceGroup().location
sku: {
name: 'Standard'
}
properties: {
publicIPAllocationMethod: 'Static'
idleTimeoutInMinutes: 4
}
}
resource networkSecurityGroup 'Microsoft.Network/networkSecurityGroups@2023-09-01' = {
name: 'nsg-web-01'
location: resourceGroup().location
properties: {
securityRules: [
{
name: 'SSH'
properties: {
priority: 100
direction: 'Inbound'
access: 'Allow'
protocol: 'Tcp'
sourcePortRange: '*'
destinationPortRange: '22'
sourceAddressPrefix: '*' // Consider restricting this to known IPs in production
destinationAddressPrefix: '*'
}
}
]
}
}
resource nic 'Microsoft.Network/networkInterfaces@2023-09-01' = {
name: 'nic-web-01'
location: resourceGroup().location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
privateIPAllocationMethod: 'Dynamic'
subnet: {
id: vnet.properties.subnets[0].id
}
publicIPAddress: {
id: publicIp.id
}
}
}
]
networkSecurityGroup: {
id: networkSecurityGroup.id
}
}
}
resource vm 'Microsoft.Compute/virtualMachines@2023-09-01' = {
name: 'vm-web-01'
location: resourceGroup().location
properties: {
hardwareProfile: {
vmSize: 'Standard_B2s'
}
osProfile: {
computerName: 'web01'
adminUsername: 'azureuser'
linuxConfiguration: {
disablePasswordAuthentication: true
ssh: {
publicKeys: [
{
path: '/home/azureuser/.ssh/authorized_keys'
keyData: 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj... user@example.com' // Replace with your actual public key
}
]
}
}
}
storageProfile: {
imageReference: {
publisher: 'Canonical'
offer: '0001-com-ubuntu-server-focal'
sku: '20_04-lts-gen2'
version: 'latest'
}
osDisk: {
createOption: 'FromImage'
managedDisk: {
storageAccountType: 'Standard_LRS'
}
diskSizeGB: 30 // Default for many Linux images, but can be customized
}
}
networkProfile: {
networkInterfaces: [
{
id: nic.id
}
]
}
}
}
Core VM Properties and OS Configuration
The Microsoft.Compute/virtualMachines resource defines the virtual machine itself. Key properties include:
name: A unique name for the VM within the resource group.location: Typically inherited from the resource group viaresourceGroup().location.hardwareProfile.vmSize: Defines the VM's SKU, e.g., 'Standard_B2s', 'Standard_D2s_v3'. Choose based on CPU, memory, and I/O requirements. For testing, 'Standard_B1s' is often sufficient, but 'Standard_B2s' offers a good balance for light workloads.osProfile.computerName: The hostname of the VM.osProfile.adminUsername: The default administrative username for SSH access. For Linux, 'azureuser' is common.osProfile.linuxConfiguration: Specific settings for Linux VMs.disablePasswordAuthentication: Set totrueto enforce SSH key-based authentication, a security best practice.ssh.publicKeys: An array defining SSH public keys. Thepathspecifies where the key will be installed (e.g.,/home/azureuser/.ssh/authorized_keys), andkeyDatacontains the actual public key string. Ensure this is correctly formatted (e.g.,ssh-rsa AAAAB...).
Networking Configuration
A VM cannot exist in isolation; it requires a network interface (NIC) connected to a subnet within a Virtual Network (VNet). Public IP addresses and Network Security Groups (NSGs) manage external connectivity and firewall rules.
Virtual Network and Subnet
The Microsoft.Network/virtualNetworks resource defines your private network space in Azure. A VNet contains one or more subnets.
addressSpace.addressPrefixes: Defines the CIDR block for the VNet (e.g., '10.0.0.0/16').subnets: An array of subnets within the VNet, each with its ownaddressPrefix(e.g., '10.0.1.0/24').
Network Security Group (NSG)
Microsoft.Network/networkSecurityGroups acts as a virtual firewall for your VM. It controls inbound and outbound traffic at the NIC or subnet level.
securityRules: An array of rules. Each rule defines:name: Unique rule identifier.priority: Lower numbers are processed first (100-4096).direction: 'Inbound' or 'Outbound'.access: 'Allow' or 'Deny'.protocol: 'Tcp', 'Udp', 'Icmp', or '*'.sourcePortRange/destinationPortRange: Port numbers or ranges.sourceAddressPrefix/destinationAddressPrefix: CIDR blocks, 'VirtualNetwork', 'Internet', or service tags. For SSH, restrictsourceAddressPrefixto your office IP for better security instead of '*'.
Public IP Address
A Microsoft.Network/publicIPAddresses resource allows direct internet access to your VM.
sku.name: 'Basic' or 'Standard'. 'Standard' is recommended for production due to better features and security.publicIPAllocationMethod: 'Dynamic' or 'Static'. 'Static' ensures the IP address doesn't change after a VM deallocation.
Network Interface (NIC)
The Microsoft.Network/networkInterfaces resource connects the VM to the VNet and associates it with an NSG and optionally a Public IP.
ipConfigurations: Defines the private and public IP settings.privateIPAllocationMethod: 'Dynamic' or 'Static'. 'Dynamic' is common for most VMs.subnet.id: References the ID of a subnet defined in the VNet.publicIPAddress.id: References the ID of a public IP address resource.
networkSecurityGroup.id: References the ID of an NSG.
Storage Configuration
The storageProfile section within the VM resource defines the OS disk and optionally data disks.
imageReference: Specifies the base OS image.publisher: e.g., 'Canonical', 'MicrosoftWindowsServer'.offer: e.g., '0001-com-ubuntu-server-focal', 'WindowsServer'.sku: e.g., '20_04-lts-gen2', '2019-Datacenter'.version: 'latest' is common, or a specific version number.
osDisk: Configuration for the operating system disk.createOption: 'FromImage' for new VMs.managedDisk.storageAccountType: 'Standard_LRS', 'Premium_LRS', 'StandardSSD_LRS', 'UltraSSD_LRS'. Choose based on performance and cost. 'Standard_LRS' is suitable for most non-production Linux VMs.diskSizeGB: Size of the OS disk in GB. Note that some images have a default minimum.
dataDisks: (Optional) An array for attaching additional data disks. Each entry would define properties like LUN, createOption, managedDisk type, and diskSizeGB.
Deployment via Azure CLI
Once your Bicep file (e.g., main.bicep) is ready, deploy it using the Azure CLI. You need a target resource group.
# Create a resource group if it doesn't exist
az group create --name rg-web --location eastus
# Deploy the Bicep template
az deployment group create \
--resource-group rg-web \
--template-file main.bicep \
--name web-vm-deployment-001
The --template-file argument points to your Bicep file. The --name argument gives a name to the deployment operation itself, which is useful for tracking in the Azure portal or CLI.
Outputs (Optional but Recommended)
You can define outputs in your Bicep file to retrieve important information after deployment, such as the VM's public IP address or FQDN. This is particularly useful for scripting subsequent actions.
output vmPublicIP string = publicIp.properties.ipAddress
output vmFQDN string = publicIp.properties.dnsSettings.fqdn
After deployment, you can query outputs:
az deployment group show -g rg-web -n web-vm-deployment-001 --query properties.outputs
Common Pitfalls
- Incorrect SSH Public Key Format: Ensure the
keyDatainlinuxConfiguration.ssh.publicKeysis a single, continuous string, including thessh-rsaprefix and any comments. It must be a valid public key. - NSG Rules Too Permissive: Using
sourceAddressPrefix: '*'for SSH (port 22) exposes your VM to the entire internet. Restrict this to your specific IP address ranges. - Dependency Issues: While Bicep infers many dependencies, complex scenarios might require explicit dependencies using the
dependsOnproperty, though this is less common with well-structured Bicep. - VM Size Availability: Not all VM sizes are available in all regions. Check Azure documentation for regional availability if you encounter deployment errors related to VM size.
- Image SKU Not Found: Double-check the
publisher,offer,sku, andversionfor your desired OS image. These are case-sensitive. You can list available images usingaz vm image list --publisher Canonical --offer 0001-com-ubuntu-server-focal --output table. - Quota Limits: Azure subscriptions have default quotas for resources like CPU cores, public IPs, and disks. If you hit a quota limit, the deployment will fail. You may need to request a quota increase.