03 · Virtual Machines¶
Azure Virtual Machines (VMs) are the IaaS building block — a full Linux or Windows operating system running on Microsoft's hardware, billed by the second while it's running. This module creates a Linux VM, connects to it over SSH, and covers the networking rules (NSGs) that control what traffic can reach it.
VM sizes¶
Every VM has a size (also called a SKU) that determines its vCPUs, RAM, and disk/network performance tier. Sizes are grouped into families by workload:
| Family | Example size | Good for |
|---|---|---|
| B-series (burstable) | Standard_B1s |
Free-tier eligible, low steady-state CPU with burst credit — ideal for learning/dev. |
| D-series (general purpose) | Standard_D2s_v5 |
Balanced CPU/memory for typical web apps and services. |
| F-series (compute optimized) | Standard_F2s_v2 |
CPU-heavy workloads like batch processing. |
| E-series (memory optimized) | Standard_E2s_v5 |
In-memory caches, larger databases. |
Standard_B1s (1 vCPU, 1 GiB RAM) is covered by the Always-Free tier's 750
hours/month of B1s Linux or Windows VM usage — use it for every exercise in
this course.
Create a Linux VM¶
az group create --name rg-vm-demo --location eastus
az vm create \
--resource-group rg-vm-demo \
--name vm-demo \
--image Ubuntu2204 \
--size Standard_B1s \
--admin-username azureuser \
--generate-ssh-keys
--image Ubuntu2204picks an Ubuntu 22.04 LTS marketplace image (runaz vm image list --output table --allto browse others).--generate-ssh-keyscreates (or reuses) an SSH key pair at~/.ssh/id_rsaand installs the public key on the VM — no password authentication needed.- This single command also creates, for free, a virtual network, subnet, public IP, and network security group as defaults, so a bare VM is reachable immediately. In Module 05 you'll create these explicitly instead of relying on the defaults.
The command prints JSON on completion including the VM's public IP:
Connect over SSH¶
ssh azureuser@20.xx.xx.xx
# Or let the CLI look the IP up for you:
az vm show --resource-group rg-vm-demo --name vm-demo \
--show-details --query publicIps --output tsv
Windows VMs use RDP (Remote Desktop Protocol) instead — port 3389 rather
than SSH's 22 — connected to via the Remote Desktop client using the
admin username/password you set at creation (--admin-password, since RDP
doesn't use SSH keys).
Network Security Groups (NSGs)¶
An NSG is a stateful firewall of allow/deny rules, evaluated by priority, attached to a subnet and/or a VM's network interface. The default VM creation above attaches an NSG that allows inbound SSH (port 22) from anywhere and denies everything else inbound by default.
# Show the NSG created for your VM
az network nsg list --resource-group rg-vm-demo --output table
# List its rules
az network nsg rule list \
--resource-group rg-vm-demo \
--nsg-name vm-demoNSG \
--output table
# Open port 80 for a web server you'll install
az network nsg rule create \
--resource-group rg-vm-demo \
--nsg-name vm-demoNSG \
--name allow-http \
--priority 320 \
--destination-port-ranges 80 \
--protocol Tcp \
--access Allow
Rules are evaluated in ascending priority order (lower number = higher precedence) and the first match wins — leave gaps between priorities (100, 200, 300...) so you can insert new rules later without renumbering.
Restrict SSH/RDP in anything beyond a lab
The default SSH rule allows any source IP. For real use, restrict
--source-address-prefixes to your own IP (curl ifconfig.me to find
it) instead of leaving port 22/3389 open to the entire internet, which
is a constant scanning target for automated attacks.
# Tighten the default SSH rule to just your IP
az network nsg rule update \
--resource-group rg-vm-demo \
--nsg-name vm-demoNSG \
--name default-allow-ssh \
--source-address-prefixes "$(curl -s ifconfig.me)/32"
Managing the VM¶
# Stop (deallocate) the VM -- stops billing for compute, keeps the disk
az vm deallocate --resource-group rg-vm-demo --name vm-demo
# Start it again
az vm start --resource-group rg-vm-demo --name vm-demo
# Restart
az vm restart --resource-group rg-vm-demo --name vm-demo
# Resize (must be stopped first if the new size isn't available on the
# current hardware cluster)
az vm resize --resource-group rg-vm-demo --name vm-demo --size Standard_B2s
# See running state
az vm get-instance-view --resource-group rg-vm-demo --name vm-demo \
--query instanceView.statuses --output table
az vm stop only shuts down the OS but Azure keeps billing the compute
allocation; use az vm deallocate to actually release the hardware
reservation and stop compute charges (the disk still incurs its own small
storage charge either way).
Cleanup¶
Deleting the resource group removes the VM, its disk, public IP, NIC, and NSG together — the fastest way to make sure nothing keeps billing.
How It Actually Works¶
When you run az vm create, ARM doesn't provision a VM directly — it hands
the request to the Compute Resource Provider, which schedules the VM
onto a physical host in an Azure datacenter cluster (a "scale unit" of
racks managed by the Azure Fabric Controller). The VM itself is a set of
independent ARM sub-resources wired together: a Network Interface Card
(NIC), one or more Managed Disks, and the VM object referencing both — this
is why az vm show can return NIC and disk IDs as separate resource
references rather than embedded data. A Managed Disk is itself a
page-blob stored in Azure Storage, but Azure hides the storage account from
you and instead exposes a fixed IOPS/throughput SKU (Standard HDD, Standard
SSD, Premium SSD, Ultra) — the disk is replicated per the SKU's storage
redundancy setting independently of the VM's own lifecycle.
VM sizing (Standard_B1s, Standard_D2s_v5, etc.) maps to a specific
physical host generation and NUMA/vCPU:memory ratio; when you resize a VM,
Azure has to find capacity for the new size on hardware in the same
placement group, which is why a resize can fail with an
OverconstrainedAllocationRequest on a busy region. The public IP and NSG
rules you attach live at the network layer, enforced by Azure's
software-defined networking (SDN) stack on the host's virtual switch before
traffic ever reaches the VM's virtual NIC — so an NSG deny rule blocks a
packet without the guest OS ever seeing it. az vm deallocate releases the
compute allocation (you stop paying for the VM size) while the disks persist
in storage and keep billing separately — this is the actual mechanical
reason "stopped" (still allocated) and "deallocated" bill differently.
Cheat sheet¶
| Command | Purpose |
|---|---|
az vm list-sizes --location <region> |
List available VM sizes in a region. |
az vm create -g -n --image --size --admin-username --generate-ssh-keys |
Create a Linux VM with SSH key auth. |
az vm show --show-details --query publicIps -o tsv |
Get a VM's public IP. |
az network nsg rule list -g --nsg-name |
List NSG rules. |
az network nsg rule create ... --priority --destination-port-ranges --access |
Add an NSG rule. |
az vm deallocate -g -n |
Stop the VM and release compute billing. |
az vm start / az vm restart |
Start / restart a VM. |
az vm resize -g -n --size |
Change a VM's size. |
Exercise¶
- Create a resource group
rg-vm-laband aStandard_B1sUbuntu VM in it. - SSH into the VM and run
sudo apt update && sudo apt install -y nginxto install a web server. - Add an NSG rule opening port 80, then visit
http://<public-ip>/in a browser to see the nginx welcome page. - Tighten the SSH rule to your own IP address only.
az vm deallocatethe VM, confirm its status shows "deallocated" withaz vm get-instance-view, then delete the resource group.