Provision the VM
Create the VM, then push in swap, Node, Claude Code and the two tenant folders, all in one shot.
- Sign
azin on a device Conditional Access trusts - Create the VM in an Anthropic-supported region
- Auto-run the in-VM setup (swap, Node, Claude Code,
~/.claude-work+~/.claude-vol)
Money, what Step 1 costs
A Standard_B2als_v2 (2 vCPU / 4 GiB) running 24/7 is ≈ US$34.5/mo (live retail price, Southeast Asia, Linux PAYG); with a 64 GB Premium SSD (~US$10/mo) and the static public IP from Step 2 (~US$3-4/mo) that's ≈ US$48/mo. I run it on a capped sponsorship subscription, so I watch the credit balance closely. A Reserved-Instance / Savings Plan trims compute ~30-40%.
- Run the build from a device Conditional Access trusts. A fresh, interactive
az login(with MFA) refreshes the token cache; automation/unregistered machines get blocked on writes. - Choose an Anthropic-supported region (Southeast Asia, India, etc.). Not East Asia / Hong Kong, see the gotcha below.
$Subscription = "<SUBSCRIPTION_ID>" $Tenant = "<TENANT_ID>" $RG = "<RESOURCE_GROUP>" # e.g. claude $VM = "<VM_NAME>" # e.g. vm-claude-host $Location = "southeastasia" # MUST be Anthropic-supported (not eastasia) $Size = "Standard_B2als_v2" # 4 GiB; B2ats_v2 (1 GiB) is too small $Admin = "azureadmin" $PubKey = "$env:USERPROFILE\.ssh\vm-claude-host_ed25519.pub"
az login --tenant $Tenant az account set --subscription $Subscription
az vm create --resource-group $RG --name $VM --location $Location --image Ubuntu2404 --size $Size --os-disk-size-gb 64 --storage-sku Premium_LRS --public-ip-address "" --nsg-rule NONE --admin-username $Admin --ssh-key-values $PubKey
az vm run-command invoke --resource-group $RG --name $VM --command-id RunShellScript --scripts "@vm-setup.sh"⤓ vm-setup.sh (the in-VM script this pushes)
# Phase 2, 4 GiB swap so a small VM survives load fallocate -l 4G /swapfile && chmod 600 /swapfile && mkswap /swapfile && swapon /swapfile grep -q '^/swapfile' /etc/fstab || echo '/swapfile none swap sw 0 0' >> /etc/fstab sysctl vm.swappiness=60 # Phase 4, Node 20 LTS + Claude Code CLI curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && apt-get install -y nodejs git tmux npm install -g @anthropic-ai/claude-code # Phase 5, one config directory per tenant install -d -o azureadmin -g azureadmin ~azureadmin/.claude-work ~azureadmin/.claude-vol
What bit me, the region 403 that forced a rebuild
I first built in East Asia (Hong Kong). Claude Code simply wouldn't connect, api.anthropic.com returns HTTP 403 from unsupported regions (HK/China). Fix: rebuild in Southeast Asia (Singapore). Verify from the VM before trusting it: curl -s -o /dev/null -w '%{http_code}' https://api.anthropic.com/, you want anything but 403.
What bit me, az blocked by Conditional Access
My automation workstation's az couldn't write until an interactive MFA az login refreshed the shared token cache (the Azure MCP could read but not create). Fix: run the build from a registered/compliant device, or a portal Cloud Shell.
What bit me, 1 GiB RAM was too small
The directed B2ats_v2 (1 GiB) OOM-killed builds and wouldn't hold a VS Code Remote-SSH server. Fix: the 4 GiB swap file is mandatory, and I settled on B2als_v2 (4 GiB). If it still OOMs, resize to 8 GiB (Operate).
Tip, no public IP yet, on purpose
--public-ip-address "" --nsg-rule NONE creates the VM with nothing exposed. You open exactly one narrow door in Step 2, not before.