--- id: BagelHole/DevOps-Security-Agent-Skills/arm-templates version: "6aec2648" license: MIT install: manual updated: 2026-05-22 --- # arm-templates — ARM Templates & Bicep provides Azure-native infrastructure-as-code using Bicep's cleaner syntax or traditional ARM JSON. Build modular deployments across resource groups, subscriptions, and management groups with What-If validation before applying changes. Publisher: BagelHole · Stars: 44 · Updated: 2026-05-22 Install (manual): `git clone https://github.com/BagelHole/DevOps-Security-Agent-Skills` ## SKILL.md # ARM Templates & Bicep Deploy Azure infrastructure with ARM templates and Bicep. Bicep is the recommended domain-specific language that compiles to ARM JSON, offering cleaner syntax, modules, and first-class tooling support. ## When to Use - You need Azure-native Infrastructure as Code without third-party tooling. - Your organization standardizes on Azure and wants tight portal integration. - You need What-If analysis before deploying changes. - You are migrating existing ARM JSON templates to Bicep for maintainability. - You need deployment scopes at resource group, subscription, management group, or tenant level. ## Prerequisites ```bash # Install Azure CLI curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash # Install Bicep CLI (bundled with Azure CLI 2.20+) az bicep install az bicep upgrade # Verify installation az bicep version # Login and set subscription az login az account set --subscription "my-subscription-id" ``` ## Bicep Fundamentals ### Resource Group Deployment with Virtual Network ```bicep // main.bicep @description('Azure region for all resources') param location string = resourceGroup().location @description('Environment name used for resource naming') @allowed(['dev', 'staging', 'prod']) param environment string = 'dev' @description('Base name for all resources') param baseName string var vnetName = '${baseName}-${environment}-vnet' var nsgName = '${baseName}-${environment}-nsg' resource nsg 'Microsoft.Network/networkSecurityGroups@2023-05-01' = { name: nsgName location: location properties: { securityRules: [ { name: 'AllowHTTPS' properties: { priority: 100 direction: 'Inbound' access: 'Allow' protocol: 'Tcp' sourcePortRange: '*' destinationPortRange: '443' sourceAddressPrefix: '*' destinationAddressPrefix: '*' } } { name: 'DenyAllInbound' properties: { priority: 4096 direction: 'Inbound' access: 'Deny' protocol: '*' sourcePortRange: '*' destinationPortRange: '*' sourceAddressPrefix: '*' destinationAddressPrefix: '*' } } ] } } resource vnet 'Microsoft.Network/virtualNetworks@2023-05-01' = { name: vnetName location: location properties: { addressSpace: { addressPrefixes: [ '10.0.0.0/16' ] } subnets: [ { name: 'web-subnet' properties: { addressPrefix: '10.0.1.0/24' networkSecurityGroup: { id: nsg.id } } } { name: 'app-subnet' properties: { addressPrefix: '10.0.2.0/24' } } { name: 'data-subnet' properties: { addressPrefix: '10.0.3.0/24' privateEndpointNetworkPolicies: 'Enabled' } } ] } } output vnetId string = vnet.id output webSubnetId string = vnet.properties.subnets[0].id output appSubnetId string = vnet.properties.subnets[1].id ``` ### VM Deployment with Managed Identity ```bicep // vm.bicep param location string = resourceGroup().location param vmName string param subnetId string param adminUsername string = 'azureuser' @secure() param adminPublicKey string resource nic 'Microsoft.Network/networkInterfaces@2023-05-01' = { name: '${vmName}-nic' location: location properties: { ipConfigurations: [ { name: 'ipconfig1' properties: { privateIPAllocationMethod: 'Dynamic' subnet: { id: subnetId } } } ] } } resource vm 'Microsoft.Compute/virtualMachines@2023-07-01' = { name: vmName location: location identity: { type: 'SystemAssigned' } properties: { hardwareProfile: { vmSize: 'Standard_B2s' } osProfile: { computerName: vmName adminUsername: adminUsername linuxConfiguration: { disablePasswordAuthentication: true ssh: { publicKeys: [ { path: '/home/${adminUsername}/.ssh/authorized_keys' keyData: adminPublicKey } ] } } } storageProfile: { imageReference: { publisher: 'Canonical' offer: '0001-com-ubuntu-server-jammy' sku: '22_04-lts-gen2' version: 'latest' } osDisk: { createOption: 'FromImage' managedDisk: { storageAccountType: 'Premium_LRS' } } } networkProfile: { networkInterfaces: [ { id: nic.id } ] } diagnosticsProfile: { bootDiagnostics: { enabled: true } } } } output vmPrincipalId string = vm.identity.principalId output vmId string = vm.id ``` ## Bicep Modules ### Module Definition ```bicep // modules/storage.bicep @description('Storage account name (3-24 chars, lowercase alphanumeric)') param storageAccountName string param location string = resourceGroup().location param sku string = 'Standard_LRS' @allowed(['Hot', 'Cool', 'Archive']) param accessTier string = 'Hot' resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { name: storageAccountName location: location sku: { name: sku } kind: 'StorageV2' properties: { accessTier: accessTier supportsHttpsTrafficOnly: true minimumTlsVersion: 'TLS1_2' allowBlobPublicAccess: false networkAcls: { defaultAction: 'Deny' bypass: 'AzureServices' } } } output storageAccountId string = storageAccount.id output primaryBlobEndpoint string = storageAccount.properties.primaryEndpoints.blob ``` ### Consuming Modules ```bicep // main.bicep param location string = resourceGroup().location param environment string = 'prod' module storage 'modules/storage.bicep' = { name: 'storage-deployment' params: { storageAccountName: 'myapp${environment}sa' location: location sku: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS' } } module vnet 'modules/network.bicep' = { name: 'vnet-deployment' params: { location: location environment: environment } } // Reference module outputs output storageBlobEndpoint string = storage.outputs.primaryBlobEndpoint ``` ## ARM JSON Template Structure ```json { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "storageAccountName": { "type": "string", "metadata": { "description": "Name of the storage account" } }, "location": { "type": "string", "defaultValue": "[resourceGroup().location]" } }, "variables": { "storageSku": "Standard_LRS" }, "resources": [ { "type": "Microsoft.Storage/storageAccounts", "apiVersion": "2023-01-01", "name": "[parameters('storageAccountName')]", "location": "[parameters('location')]", "sku": { "name": "[variables('storageSku')]" }, "kind": "StorageV2", "properties": { "supportsHttpsTrafficOnly": true, "minimumTlsVersion": "TLS1_2" } } ], "outputs": { "storageId": { "type": "string", "value": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccountName'))]" } } } ``` ## Deployment Commands ```bash # Validate a Bicep template before deployment az deployment group validate \ --resource-group mygroup \ --template-file main.bicep \ --parameters environment='prod' baseName='myapp' # Preview changes with What-If az deployment group what-if \ --resource-group mygroup \ --template-file main.bicep \ --parameters environment='prod' baseName='myapp' # Deploy Bicep to resource group az deployment group create \ --resource-group mygroup \ --template-file main.bicep \ --parameters environment='prod' baseName='myapp' \ --name "deploy-$(date +%Y%m%d-%H%M%S)" # Deploy ARM JSON with parameter file az deployment group create \ --resource-group mygroup \ --template-file template.json \ --parameters @parameters.prod.json # Subscription-level deployment (e.g., resource groups, policies) az deployment sub create \ --location eastus \ --template-file subscription-level.bicep \ --parameters @params.json # Management group deployment az deployment mg create \ --management-group-id my-mg \ --location eastus \ --template-file mg-policy.bicep # Export resource group to ARM JSON az group export --name mygroup --output json > exported-template.json # Decompile ARM JSON to Bicep az bicep decompile --file exported-template.json # Build Bicep to ARM JSON (for inspection) az bicep build --file main.bicep --outfile main.json # List deployments and their status az deployment group list \ --resource-group mygroup \ --output table # Delete a failed deployment az deployment group delete \ --resource-group mygroup \ --name my-failed-deployment ``` ## Parameter Files ```json // parameters.prod.json { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "environment": { "value": "prod" }, "baseName": { "value": "myapp" }, "adminPublicKey": { "reference": { "keyVault": { "id": "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.KeyVault/vaults/{vault}" }, "secretName": "ssh-public-key" } } } } ``` ## Linked and Nested Templates ```bicep // Deploy to a different resource group module networkInSharedRg 'modules/network.bicep' = { name: 'shared-network' scope: resourceGroup('shared-networking-rg') params: { location: location } } // Conditional deployment param deployMonitoring bool = true module monitoring 'modules/monitoring.bicep' = if (deployMonitoring) { name: 'monitoring-deployment' params: { location: location } } // Loop deployment param storageAccounts array = [ { name: 'logs', sku: 'Standard_LRS' } { name: 'data', sku: 'Standard_GRS' } ] module storageLoop 'modules/storage.bicep' = [for account in storageAccounts: { name: 'storage-${account.name}' params: { storageAccountName: '${baseName}${account.name}sa' sku: account.sku location: location } }] ``` ## Troubleshooting | Symptom | Cause | Fix | |---------|-------|-----| | `InvalidTemplate` error | Syntax error in ARM JSON or Bicep | Run `az bicep build` to check for compile errors | | `ResourceNotFound` during deployment | Resource dependency not declared | Add `dependsOn` or use implicit references in Bicep | | `DeploymentFailed` with quota error | Subscription quota exceeded | Request quota increase or use a different region | | `AuthorizationFailed` | Insufficient RBAC permissions | Assign Contributor role on the target resource group | | Parameter file secrets in source control | Secrets stored as plain text | Use Key Vault references in parameter files | | Deployment takes very long | Large number of resources deployed serially | Use `dependsOn` carefully to allow parallel deployment | | `What-If` shows unexpected deletions | Complete mode instead of Incremental | Use `--mode Incremental` (the default) to avoid deleting unmanaged resources | | Bicep module not found | Incorrect relative path | Verify path is relative to the consuming file | ## Related Skills - `terraform-azure` -- Multi-cloud IaC alternative with broader provider support. - `azure-networking` -- VNet, NSG, and firewall configurations referenced in templates. - `azure-vms` -- Virtual machine sizing and configuration details. - `azure-aks` -- Kubernetes cluster definitions for Bicep/ARM. [View on SkillFed](https://skillfed.io/BagelHole/DevOps-Security-Agent-Skills/arm-templates) · [View on GitHub](https://github.com/BagelHole/DevOps-Security-Agent-Skills)