From 191e1947c1b1ec6f5c819c8ec20150697f14acbb Mon Sep 17 00:00:00 2001 From: Johnson Shi <13926417+johnsonshi@users.noreply.github.com> Date: Thu, 19 Mar 2026 06:15:06 -0700 Subject: [PATCH] docs: add Azure VM deployment guide with in-repo ARM templates and bootstrap script (#47898) * docs: add Azure Linux VM install guide * docs: move Azure guide into dedicated docs/install/azure layout * docs: polish Azure guide onboarding and reference links * docs: address Azure review feedback on bootstrap safety * docs: format azure ARM template * docs: flatten Azure install docs and move ARM assets --- docs/docs.json | 13 + docs/install/azure.md | 169 +++++++++ docs/platforms/index.md | 1 + docs/vps.md | 3 +- infra/azure/templates/azuredeploy.json | 340 ++++++++++++++++++ .../templates/azuredeploy.parameters.json | 48 +++ 6 files changed, 573 insertions(+), 1 deletion(-) create mode 100644 docs/install/azure.md create mode 100644 infra/azure/templates/azuredeploy.json create mode 100644 infra/azure/templates/azuredeploy.parameters.json diff --git a/docs/docs.json b/docs/docs.json index 1e5cf45d4d5..e80697ac63d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -767,6 +767,14 @@ "source": "/gcp", "destination": "/install/gcp" }, + { + "source": "/azure", + "destination": "/install/azure" + }, + { + "source": "/install/azure/azure", + "destination": "/install/azure" + }, { "source": "/platforms/fly", "destination": "/install/fly" @@ -779,6 +787,10 @@ "source": "/platforms/gcp", "destination": "/install/gcp" }, + { + "source": "/platforms/azure", + "destination": "/install/azure" + }, { "source": "/platforms/macos-vm", "destination": "/install/macos-vm" @@ -872,6 +884,7 @@ "install/fly", "install/hetzner", "install/gcp", + "install/azure", "install/macos-vm", "install/exe-dev", "install/railway", diff --git a/docs/install/azure.md b/docs/install/azure.md new file mode 100644 index 00000000000..a257059f75d --- /dev/null +++ b/docs/install/azure.md @@ -0,0 +1,169 @@ +--- +summary: "Run OpenClaw Gateway 24/7 on an Azure Linux VM with durable state" +read_when: + - You want OpenClaw running 24/7 on Azure with Network Security Group hardening + - You want a production-grade, always-on OpenClaw Gateway on your own Azure Linux VM + - You want secure administration with Azure Bastion SSH + - You want repeatable deployments with Azure Resource Manager templates +title: "Azure" +--- + +# OpenClaw on Azure Linux VM + +This guide sets up an Azure Linux VM, applies Network Security Group (NSG) hardening, configures Azure Bastion (managed Azure SSH entry point), and installs OpenClaw. + +## What you’ll do + +- Deploy Azure compute and network resources with Azure Resource Manager (ARM) templates +- Apply Azure Network Security Group (NSG) rules so VM SSH is allowed only from Azure Bastion +- Use Azure Bastion for SSH access +- Install OpenClaw with the installer script +- Verify the Gateway + +## Before you start + +You’ll need: + +- An Azure subscription with permission to create compute and network resources +- Azure CLI installed (see [Azure CLI install steps](https://learn.microsoft.com/cli/azure/install-azure-cli) if needed) + +## 1) Sign in to Azure CLI + +```bash +az login # Sign in and select your Azure subscription +az extension add -n ssh # Extension required for Azure Bastion SSH management +``` + +## 2) Register required resource providers (one-time) + +```bash +az provider register --namespace Microsoft.Compute +az provider register --namespace Microsoft.Network +``` + +Verify Azure resource provider registration. Wait until both show `Registered`. + +```bash +az provider show --namespace Microsoft.Compute --query registrationState -o tsv +az provider show --namespace Microsoft.Network --query registrationState -o tsv +``` + +## 3) Set deployment variables + +```bash +RG="rg-openclaw" +LOCATION="westus2" +TEMPLATE_URI="https://raw.githubusercontent.com/openclaw/openclaw/main/infra/azure/templates/azuredeploy.json" +PARAMS_URI="https://raw.githubusercontent.com/openclaw/openclaw/main/infra/azure/templates/azuredeploy.parameters.json" +``` + +## 4) Select SSH key + +Use your existing public key if you have one: + +```bash +SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)" +``` + +If you don’t have an SSH key yet, run the following: + +```bash +ssh-keygen -t ed25519 -a 100 -f ~/.ssh/id_ed25519 -C "you@example.com" +SSH_PUB_KEY="$(cat ~/.ssh/id_ed25519.pub)" +``` + +## 5) Select VM size and OS disk size + +Set VM and disk sizing variables: + +```bash +VM_SIZE="Standard_B2as_v2" +OS_DISK_SIZE_GB=64 +``` + +Choose a VM size and OS disk size that are available in your Azure subscription/region and matches your workload: + +- Start smaller for light usage and scale up later +- Use more vCPU/RAM/OS disk size for heavier automation, more channels, or larger model/tool workloads +- If a VM size is unavailable in your region or subscription quota, pick the closest available SKU + +List VM sizes available in your target region: + +```bash +az vm list-skus --location "${LOCATION}" --resource-type virtualMachines -o table +``` + +Check your current VM vCPU and OS disk size usage/quota: + +```bash +az vm list-usage --location "${LOCATION}" -o table +``` + +## 6) Create the resource group + +```bash +az group create -n "${RG}" -l "${LOCATION}" +``` + +## 7) Deploy resources + +This command applies your selected SSH key, VM size, and OS disk size. + +```bash +az deployment group create \ + -g "${RG}" \ + --template-uri "${TEMPLATE_URI}" \ + --parameters "${PARAMS_URI}" \ + --parameters location="${LOCATION}" \ + --parameters vmSize="${VM_SIZE}" \ + --parameters osDiskSizeGb="${OS_DISK_SIZE_GB}" \ + --parameters sshPublicKey="${SSH_PUB_KEY}" +``` + +## 8) SSH into the VM through Azure Bastion + +```bash +RG="rg-openclaw" +VM_NAME="vm-openclaw" +BASTION_NAME="bas-openclaw" +ADMIN_USERNAME="openclaw" +VM_ID="$(az vm show -g "${RG}" -n "${VM_NAME}" --query id -o tsv)" + +az network bastion ssh \ + --name "${BASTION_NAME}" \ + --resource-group "${RG}" \ + --target-resource-id "${VM_ID}" \ + --auth-type ssh-key \ + --username "${ADMIN_USERNAME}" \ + --ssh-key ~/.ssh/id_ed25519 +``` + +## 9) Install OpenClaw (in the VM shell) + +```bash +curl -fsSL https://openclaw.ai/install.sh -o /tmp/openclaw-install.sh +bash /tmp/openclaw-install.sh +rm -f /tmp/openclaw-install.sh +openclaw --version +``` + +The installer script handles Node detection/installation and runs onboarding by default. + +## 10) Verify the Gateway + +After onboarding completes: + +```bash +openclaw gateway status +``` + +Most enterprise Azure teams already have GitHub Copilot licenses. If that is your case, we recommend choosing the GitHub Copilot provider in the OpenClaw onboarding wizard. See [GitHub Copilot provider](/providers/github-copilot). + +The included ARM template uses Ubuntu image `version: "latest"` for convenience. If you need reproducible builds, pin a specific image version in `infra/azure/templates/azuredeploy.json` (you can list versions with `az vm image list --publisher Canonical --offer ubuntu-24_04-lts --sku server --all -o table`). + +## Next steps + +- Set up messaging channels: [Channels](/channels) +- Pair local devices as nodes: [Nodes](/nodes) +- Configure the Gateway: [Gateway configuration](/gateway/configuration) +- For more details on OpenClaw Azure deployment with the GitHub Copilot model provider: [OpenClaw on Azure with GitHub Copilot](https://github.com/johnsonshi/openclaw-azure-github-copilot) diff --git a/docs/platforms/index.md b/docs/platforms/index.md index ec2663aefe4..37a0a47a6fb 100644 --- a/docs/platforms/index.md +++ b/docs/platforms/index.md @@ -29,6 +29,7 @@ Native companion apps for Windows are also planned; the Gateway is recommended v - Fly.io: [Fly.io](/install/fly) - Hetzner (Docker): [Hetzner](/install/hetzner) - GCP (Compute Engine): [GCP](/install/gcp) +- Azure (Linux VM): [Azure](/install/azure) - exe.dev (VM + HTTPS proxy): [exe.dev](/install/exe-dev) ## Common links diff --git a/docs/vps.md b/docs/vps.md index 66c2fdaf93f..9847f88e98d 100644 --- a/docs/vps.md +++ b/docs/vps.md @@ -1,5 +1,5 @@ --- -summary: "VPS hosting hub for OpenClaw (Oracle/Fly/Hetzner/GCP/exe.dev)" +summary: "VPS hosting hub for OpenClaw (Oracle/Fly/Hetzner/GCP/Azure/exe.dev)" read_when: - You want to run the Gateway in the cloud - You need a quick map of VPS/hosting guides @@ -19,6 +19,7 @@ deployments work at a high level. - **Fly.io**: [Fly.io](/install/fly) - **Hetzner (Docker)**: [Hetzner](/install/hetzner) - **GCP (Compute Engine)**: [GCP](/install/gcp) +- **Azure (Linux VM)**: [Azure](/install/azure) - **exe.dev** (VM + HTTPS proxy): [exe.dev](/install/exe-dev) - **AWS (EC2/Lightsail/free tier)**: works well too. Video guide: [https://x.com/techfrenAJ/status/2014934471095812547](https://x.com/techfrenAJ/status/2014934471095812547) diff --git a/infra/azure/templates/azuredeploy.json b/infra/azure/templates/azuredeploy.json new file mode 100644 index 00000000000..41157feec46 --- /dev/null +++ b/infra/azure/templates/azuredeploy.json @@ -0,0 +1,340 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "location": { + "type": "string", + "defaultValue": "westus2", + "metadata": { + "description": "Azure region for all resources. Any valid Azure region is allowed (no allowedValues restriction)." + } + }, + "vmName": { + "type": "string", + "defaultValue": "vm-openclaw", + "metadata": { + "description": "OpenClaw VM name." + } + }, + "vmSize": { + "type": "string", + "defaultValue": "Standard_B2as_v2", + "metadata": { + "description": "Azure VM size for OpenClaw host." + } + }, + "adminUsername": { + "type": "string", + "defaultValue": "openclaw", + "minLength": 1, + "maxLength": 32, + "metadata": { + "description": "Linux admin username." + } + }, + "sshPublicKey": { + "type": "string", + "metadata": { + "description": "SSH public key content (for example ssh-ed25519 ...)." + } + }, + "vnetName": { + "type": "string", + "defaultValue": "vnet-openclaw", + "metadata": { + "description": "Virtual network name." + } + }, + "vnetAddressPrefix": { + "type": "string", + "defaultValue": "10.40.0.0/16", + "metadata": { + "description": "Address space for the virtual network." + } + }, + "vmSubnetName": { + "type": "string", + "defaultValue": "snet-openclaw-vm", + "metadata": { + "description": "Subnet name for OpenClaw VM." + } + }, + "vmSubnetPrefix": { + "type": "string", + "defaultValue": "10.40.2.0/24", + "metadata": { + "description": "Address prefix for VM subnet." + } + }, + "bastionSubnetPrefix": { + "type": "string", + "defaultValue": "10.40.1.0/26", + "metadata": { + "description": "Address prefix for AzureBastionSubnet (must be /26 or larger)." + } + }, + "nsgName": { + "type": "string", + "defaultValue": "nsg-openclaw-vm", + "metadata": { + "description": "Network security group for VM subnet." + } + }, + "nicName": { + "type": "string", + "defaultValue": "nic-openclaw-vm", + "metadata": { + "description": "NIC for OpenClaw VM." + } + }, + "bastionName": { + "type": "string", + "defaultValue": "bas-openclaw", + "metadata": { + "description": "Azure Bastion host name." + } + }, + "bastionPublicIpName": { + "type": "string", + "defaultValue": "pip-openclaw-bastion", + "metadata": { + "description": "Public IP used by Bastion." + } + }, + "osDiskSizeGb": { + "type": "int", + "defaultValue": 64, + "minValue": 30, + "maxValue": 1024, + "metadata": { + "description": "OS disk size in GiB." + } + } + }, + "variables": { + "bastionSubnetName": "AzureBastionSubnet" + }, + "resources": [ + { + "type": "Microsoft.Network/networkSecurityGroups", + "apiVersion": "2023-11-01", + "name": "[parameters('nsgName')]", + "location": "[parameters('location')]", + "properties": { + "securityRules": [ + { + "name": "AllowSshFromAzureBastionSubnet", + "properties": { + "priority": 100, + "access": "Allow", + "direction": "Inbound", + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "22", + "sourceAddressPrefix": "[parameters('bastionSubnetPrefix')]", + "destinationAddressPrefix": "*" + } + }, + { + "name": "DenyInternetSsh", + "properties": { + "priority": 110, + "access": "Deny", + "direction": "Inbound", + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "22", + "sourceAddressPrefix": "Internet", + "destinationAddressPrefix": "*" + } + }, + { + "name": "DenyVnetSsh", + "properties": { + "priority": 120, + "access": "Deny", + "direction": "Inbound", + "protocol": "Tcp", + "sourcePortRange": "*", + "destinationPortRange": "22", + "sourceAddressPrefix": "VirtualNetwork", + "destinationAddressPrefix": "*" + } + } + ] + } + }, + { + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2023-11-01", + "name": "[parameters('vnetName')]", + "location": "[parameters('location')]", + "properties": { + "addressSpace": { + "addressPrefixes": ["[parameters('vnetAddressPrefix')]"] + }, + "subnets": [ + { + "name": "[variables('bastionSubnetName')]", + "properties": { + "addressPrefix": "[parameters('bastionSubnetPrefix')]" + } + }, + { + "name": "[parameters('vmSubnetName')]", + "properties": { + "addressPrefix": "[parameters('vmSubnetPrefix')]", + "networkSecurityGroup": { + "id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('nsgName'))]" + } + } + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('nsgName'))]" + ] + }, + { + "type": "Microsoft.Network/publicIPAddresses", + "apiVersion": "2023-11-01", + "name": "[parameters('bastionPublicIpName')]", + "location": "[parameters('location')]", + "sku": { + "name": "Standard" + }, + "properties": { + "publicIPAllocationMethod": "Static" + } + }, + { + "type": "Microsoft.Network/bastionHosts", + "apiVersion": "2023-11-01", + "name": "[parameters('bastionName')]", + "location": "[parameters('location')]", + "sku": { + "name": "Standard" + }, + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]", + "[resourceId('Microsoft.Network/publicIPAddresses', parameters('bastionPublicIpName'))]" + ], + "properties": { + "enableTunneling": true, + "ipConfigurations": [ + { + "name": "bastionIpConfig", + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), variables('bastionSubnetName'))]" + }, + "publicIPAddress": { + "id": "[resourceId('Microsoft.Network/publicIPAddresses', parameters('bastionPublicIpName'))]" + } + } + } + ] + } + }, + { + "type": "Microsoft.Network/networkInterfaces", + "apiVersion": "2023-11-01", + "name": "[parameters('nicName')]", + "location": "[parameters('location')]", + "dependsOn": ["[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]"], + "properties": { + "ipConfigurations": [ + { + "name": "ipconfig1", + "properties": { + "privateIPAllocationMethod": "Dynamic", + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('vmSubnetName'))]" + } + } + } + ] + } + }, + { + "type": "Microsoft.Compute/virtualMachines", + "apiVersion": "2023-09-01", + "name": "[parameters('vmName')]", + "location": "[parameters('location')]", + "dependsOn": ["[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]"], + "properties": { + "hardwareProfile": { + "vmSize": "[parameters('vmSize')]" + }, + "osProfile": { + "computerName": "[parameters('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": "ubuntu-24_04-lts", + "sku": "server", + "version": "latest" + }, + "osDisk": { + "createOption": "FromImage", + "diskSizeGB": "[parameters('osDiskSizeGb')]", + "managedDisk": { + "storageAccountType": "StandardSSD_LRS" + } + } + }, + "networkProfile": { + "networkInterfaces": [ + { + "id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('nicName'))]" + } + ] + }, + "diagnosticsProfile": { + "bootDiagnostics": { + "enabled": true + } + } + } + } + ], + "outputs": { + "vmName": { + "type": "string", + "value": "[parameters('vmName')]" + }, + "vmPrivateIp": { + "type": "string", + "value": "[reference(resourceId('Microsoft.Network/networkInterfaces', parameters('nicName')), '2023-11-01').ipConfigurations[0].properties.privateIPAddress]" + }, + "vnetName": { + "type": "string", + "value": "[parameters('vnetName')]" + }, + "vmSubnetName": { + "type": "string", + "value": "[parameters('vmSubnetName')]" + }, + "bastionName": { + "type": "string", + "value": "[parameters('bastionName')]" + }, + "bastionResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.Network/bastionHosts', parameters('bastionName'))]" + } + } +} diff --git a/infra/azure/templates/azuredeploy.parameters.json b/infra/azure/templates/azuredeploy.parameters.json new file mode 100644 index 00000000000..dead2e5dd3f --- /dev/null +++ b/infra/azure/templates/azuredeploy.parameters.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "location": { + "value": "westus2" + }, + "vmName": { + "value": "vm-openclaw" + }, + "vmSize": { + "value": "Standard_B2as_v2" + }, + "adminUsername": { + "value": "openclaw" + }, + "vnetName": { + "value": "vnet-openclaw" + }, + "vnetAddressPrefix": { + "value": "10.40.0.0/16" + }, + "vmSubnetName": { + "value": "snet-openclaw-vm" + }, + "vmSubnetPrefix": { + "value": "10.40.2.0/24" + }, + "bastionSubnetPrefix": { + "value": "10.40.1.0/26" + }, + "nsgName": { + "value": "nsg-openclaw-vm" + }, + "nicName": { + "value": "nic-openclaw-vm" + }, + "bastionName": { + "value": "bas-openclaw" + }, + "bastionPublicIpName": { + "value": "pip-openclaw-bastion" + }, + "osDiskSizeGb": { + "value": 64 + } + } +}