# Deploy with Azure

This guide covers deploying LinuxGuard on Azure VMs using cloud-init — the standard Linux cloud initialization mechanism. Credentials are retrieved from Azure Key Vault using system-assigned managed identity, eliminating hardcoded secrets. A Custom Script Extension alternative is provided for operators using ARM templates or the Azure Portal.

## Prerequisites

* Azure CLI installed locally for one-time setup steps
* A Linux VM with system-assigned managed identity enabled (see Step 1)
* A LinuxGuard API key and tenant ID (from the LinuxGuard console)

## Step 1: Enable managed identity and configure Key Vault

System-assigned managed identity lets the VM authenticate to Azure services without credentials. Enable it and grant it access to Key Vault using the following steps.

**Enable managed identity on an existing VM** (can also be done at creation time with `--assign-identity`):

```bash
az vm identity assign --name <VM_NAME> --resource-group <RESOURCE_GROUP>
```

**Create a Key Vault and store LinuxGuard credentials as secrets:**

```bash
# Create Key Vault (skip if you already have one)
az keyvault create --name <VAULT_NAME> --resource-group <RESOURCE_GROUP> --location <LOCATION>

# Store LinuxGuard credentials as Key Vault secrets
az keyvault secret set --vault-name <VAULT_NAME> --name linuxguard-api-key --value "<API_KEY>"
az keyvault secret set --vault-name <VAULT_NAME> --name linuxguard-tenant-id --value "<TENANT_ID>"
```

**Grant the VM's managed identity access to read secrets:**

```bash
az keyvault set-policy \
  --name <VAULT_NAME> \
  --object-id $(az vm identity show --name <VM_NAME> --resource-group <RESOURCE_GROUP> --query principalId -o tsv) \
  --secret-permissions get
```

## Step 2: Deploy with cloud-init

Cloud-init is the standard cloud VM initialization mechanism on Azure Linux VMs. Provide the configuration as user-data when creating the VM.

The IMDS (Instance Metadata Service) endpoint provides a bearer token for Key Vault access — no credentials required. Save the following as `cloud-init.yaml`, replacing `<VAULT_NAME>` with your Key Vault name:

```yaml
#cloud-config
packages:
  - jq

runcmd:
  - |
    # Configuration — replace VAULT_NAME with your Key Vault name
    VAULT_NAME="<VAULT_NAME>"

    # Acquire bearer token from IMDS (managed identity provides this — no credentials needed)
    TOKEN=$(curl -s -H "Metadata: true" \
      "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" \
      | jq -r '.access_token')

    # Retrieve credentials from Key Vault
    API_KEY=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
      "https://${VAULT_NAME}.vault.azure.net/secrets/linuxguard-api-key?api-version=7.4" \
      | jq -r '.value')
    TENANT_ID=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
      "https://${VAULT_NAME}.vault.azure.net/secrets/linuxguard-tenant-id?api-version=7.4" \
      | jq -r '.value')

    # Install LinuxGuard agent
    curl -fsSL https://packages.linuxguard.io/install-linuxguard.sh | bash -s -- --yes

    # Enroll agent — built-in guard prevents re-enrollment if already enrolled
    linuxguard-agent enroll \
      --api-key="${API_KEY}" \
      --tenant-id="${TENANT_ID}"
```

> **Note:** Azure cloud-init runs once on first boot. The agent's built-in enrollment guard provides additional protection if the script runs again. The `api-version=7.4` parameter in Key Vault REST API calls is required — omitting it returns a 400 error. See [Automated Deployment Overview](/how-to-guides/how-to/automated-deployment.md) for details on enrollment idempotency.

Create the VM and pass the cloud-init config as user-data:

```bash
az vm create \
  --name <VM_NAME> \
  --resource-group <RESOURCE_GROUP> \
  --image Ubuntu2204 \
  --custom-data cloud-init.yaml \
  --assign-identity \
  --admin-username azureuser \
  --generate-ssh-keys
```

> **Note:** `--assign-identity` enables system-assigned managed identity at VM creation time — equivalent to the `az vm identity assign` command in Step 1 for new VMs. If you use `--assign-identity` at creation time, the Step 1 identity assignment is not required.

## Alternative: Custom Script Extension

Custom Script Extension (CSE) lets you run a script on a VM after creation — useful when deploying via ARM templates or when cloud-init is not available. CSE is an Azure-specific mechanism.

The following command runs an inline script on an existing VM using CSE:

```bash
az vm extension set \
  --resource-group <RESOURCE_GROUP> \
  --vm-name <VM_NAME> \
  --name customScript \
  --publisher Microsoft.Azure.Extensions \
  --settings "{\"commandToExecute\": \"apt-get install -y jq && VAULT_NAME=<VAULT_NAME> && TOKEN=\$(curl -s -H 'Metadata: true' 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net' | jq -r '.access_token') && API_KEY=\$(curl -s -H \\\"Authorization: Bearer \$TOKEN\\\" 'https://<VAULT_NAME>.vault.azure.net/secrets/linuxguard-api-key?api-version=7.4' | jq -r '.value') && TENANT_ID=\$(curl -s -H \\\"Authorization: Bearer \$TOKEN\\\" 'https://<VAULT_NAME>.vault.azure.net/secrets/linuxguard-tenant-id?api-version=7.4' | jq -r '.value') && curl -fsSL https://packages.linuxguard.io/install-linuxguard.sh | bash -s -- --yes && linuxguard-agent enroll --api-key=\$API_KEY --tenant-id=\$TENANT_ID\"}"
```

> **Note:** For production use, store the script in Azure Blob Storage and reference it via `fileUris` rather than using an inline command — inline commands have length limits and are less maintainable.

## Verifying the Deployment

After the VM boots, enrolled servers appear in the LinuxGuard console under **Infrastructure** within a few minutes.

***

**Related**: [Automated Deployment Overview](/how-to-guides/how-to/automated-deployment.md) | [Installation](/how-to-guides/how-to/installation.md) | [Configuration](/how-to-guides/how-to/configuration.md)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.linuxguard.io/how-to-guides/how-to/automated-deployment/deploy-with-azure.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
