Deploying a Linux VM with an ARM Template

6 min·Jul 18, 2026
AI Summary

An ARM VM deployment is five resources: a virtual network with a subnet, a public IP, a network interface that binds the two, and the VM that consumes the NIC. dependsOn enforces creation order. Use SSH keys, parameterize vmSize and location, and pin apiVersion per resource.

Azure ARM Template — Linux VM Deployment
An ARM deployment template that provisions a Linux virtual machine end to end — virtual network, subnet, public IP, network interface and the VM itself — the common single-VM starting point.
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "parameters": {
    "adminUsername": {
      "type": "string",
      "metadata": {
        "description": "Admin username for the VM"
      }
    },
    "sshPublicKey": {
      "type": "securestring",
      "metadata": {
        "description": "SSH RSA public key for the admin user"
      }
    },
    "vmSize": {
      "type": "string",
      "defaultValue": "Standard_B2s"
    },
    "location": {
      "type": "string",
      "defaultValue": "[resourceGroup().location]"
    }
  },
  "variables": {
    "vnetName": "vnet-app",
    "subnetName": "subnet-app",
    "publicIpName": "pip-app",
    "nicName": "nic-app",
    "vmName": "vm-app",
    "subnetRef": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), variables('subnetName'))]"
  },
  "resources": [
    {
      "type": "Microsoft.Network/virtualNetworks",
      "apiVersion": "2023-05-01",
      "name": "[variables('vnetName')]",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "10.0.0.0/16"
          ]
        },
        "subnets": [
          {
            "name": "[variables('subnetName')]",
            "properties": {
              "addressPrefix": "10.0.0.0/24"
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Network/publicIPAddresses",
      "apiVersion": "2023-05-01",
      "name": "[variables('publicIpName')]",
      "location": "[parameters('location')]",
      "properties": {
        "publicIPAllocationMethod": "Static"
      },
      "sku": {
        "name": "Standard"
      }
    },
    {
      "type": "Microsoft.Network/networkInterfaces",
      "apiVersion": "2023-05-01",
      "name": "[variables('nicName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]",
        "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpName'))]"
      ],
      "properties": {
        "ipConfigurations": [
          {
            "name": "ipconfig1",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "subnet": {
                "id": "[variables('subnetRef')]"
              },
              "publicIPAddress": {
                "id": "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpName'))]"
              }
            }
          }
        ]
      }
    },
    {
      "type": "Microsoft.Compute/virtualMachines",
      "apiVersion": "2023-09-01",
      "name": "[variables('vmName')]",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
      ],
      "properties": {
        "hardwareProfile": {
          "vmSize": "[parameters('vmSize')]"
        },
        "osProfile": {
          "computerName": "[variables('vmName')]",
          "adminUsername": "[parameters('adminUsername')]",
          "linuxConfiguration": {
            "disablePasswordAuthentication": true,
            "ssh": {
              "publicKeys": [
                {
                  "path": "[concat('/home/', parameters('adminUsername'), '/.ssh/authorized_keys')]",
                  "keyData": "[parameters('sshPublicKey')]"
                }
              ]
            }
          }
        },
        "storageProfile": {
          "imageReference": {
            "publisher": "Canonical",
            "offer": "0001-com-ubuntu-server-jammy",
            "sku": "22_04-lts-gen2",
            "version": "latest"
          },
          "osDisk": {
            "createOption": "FromImage",
            "managedDisk": {
              "storageAccountType": "Premium_LRS"
            }
          }
        },
        "networkProfile": {
          "networkInterfaces": [
            {
              "id": "[resourceId('Microsoft.Network/networkInterfaces', variables('nicName'))]"
            }
          ]
        }
      }
    }
  ],
  "outputs": {
    "adminUsername": {
      "type": "string",
      "value": "[parameters('adminUsername')]"
    }
  }
}

An ARM template that "creates a VM" is really deploying five resources that have to line up in the right order. Here is what each one does and why the dependencies matter.

The five resources

  1. Virtual network — the private address space (10.0.0.0/16) your VM lives in.
  2. Subnet — a slice of that space (10.0.0.0/24); declared inside the VNet.
  3. Public IP — an internet-facing address, Static with the Standard SKU.
  4. Network interface (NIC) — binds the subnet and public IP together into one ipConfiguration.
  5. Virtual machine — consumes the NIC and boots from an image.

Order matters: dependsOn

ARM parallelizes deployment, so you must declare ordering explicitly. The NIC needs the VNet and public IP first:

"dependsOn": [ "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]", "[resourceId('Microsoft.Network/publicIPAddresses', variables('publicIpName'))]" ]

and the VM waits on the NIC. Skip these and the deploy fails intermittently.

Log in with keys, not passwords

Set disablePasswordAuthentication: true and pass an SSH public key as a securestring parameter:

"linuxConfiguration": { "disablePasswordAuthentication": true, "ssh": { "publicKeys": [ { "keyData": "[parameters('sshPublicKey')]" } ] } }

Parameterize the moving parts

vmSize and location change per environment — make them parameters with defaults. Everything else (names, subnet ref) can be computed variables, so one template serves dev, staging and prod.

Security notes

  • Attach a network security group and restrict SSH (22) to your office/VPN range.
  • On production, prefer a bastion or just-in-time access over a raw public IP.

Open the VM deployment template and ask the workspace to "add a network security group allowing SSH" or "add a data disk", then diff the result.

FAQ

Why do I need a NIC as a separate resource?

A VM does not attach to a subnet directly. The network interface binds the VM to a subnet and (optionally) a public IP, so it must exist before the VM references it.

How do I deploy this template?

az deployment group create --resource-group my-rg --template-file azuredeploy.json --parameters adminUsername=azureuser sshPublicKey="$(cat ~/.ssh/id_rsa.pub)".