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.