Back to Blog
5 min read

Azure Resource Manager Templates Best Practices

Azure Resource Manager (ARM) templates provide a declarative way to define and deploy Azure infrastructure. Following best practices ensures your templates are maintainable, reusable, and secure.

Template Structure

Basic Template

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "metadata": {
        "description": "Deploys a web application with SQL database",
        "author": "Michael John Pena",
        "version": "1.0.0"
    },
    "parameters": {},
    "variables": {},
    "functions": [],
    "resources": [],
    "outputs": {}
}

Parameters Best Practices

{
    "parameters": {
        "environment": {
            "type": "string",
            "allowedValues": ["dev", "staging", "prod"],
            "metadata": {
                "description": "Environment name for resource naming"
            }
        },
        "location": {
            "type": "string",
            "defaultValue": "[resourceGroup().location]",
            "metadata": {
                "description": "Location for all resources"
            }
        },
        "appServicePlanSku": {
            "type": "object",
            "defaultValue": {
                "name": "S1",
                "tier": "Standard",
                "capacity": 1
            },
            "metadata": {
                "description": "SKU configuration for App Service Plan"
            }
        },
        "sqlAdminPassword": {
            "type": "securestring",
            "metadata": {
                "description": "SQL Server admin password"
            }
        },
        "tags": {
            "type": "object",
            "defaultValue": {
                "environment": "[parameters('environment')]",
                "managedBy": "ARM Template"
            }
        }
    }
}

Variables for Naming Conventions

{
    "variables": {
        "prefix": "[concat('app', parameters('environment'))]",
        "appServicePlanName": "[concat(variables('prefix'), '-plan')]",
        "webAppName": "[concat(variables('prefix'), '-web-', uniqueString(resourceGroup().id))]",
        "sqlServerName": "[concat(variables('prefix'), '-sql-', uniqueString(resourceGroup().id))]",
        "sqlDatabaseName": "[concat(variables('prefix'), '-db')]",
        "storageAccountName": "[concat(replace(variables('prefix'), '-', ''), uniqueString(resourceGroup().id))]",
        "appInsightsName": "[concat(variables('prefix'), '-insights')]",
        "keyVaultName": "[concat(variables('prefix'), '-kv-', uniqueString(resourceGroup().id))]"
    }
}

Resource Dependencies

{
    "resources": [
        {
            "type": "Microsoft.Web/serverfarms",
            "apiVersion": "2021-02-01",
            "name": "[variables('appServicePlanName')]",
            "location": "[parameters('location')]",
            "tags": "[parameters('tags')]",
            "sku": "[parameters('appServicePlanSku')]",
            "properties": {}
        },
        {
            "type": "Microsoft.Web/sites",
            "apiVersion": "2021-02-01",
            "name": "[variables('webAppName')]",
            "location": "[parameters('location')]",
            "tags": "[parameters('tags')]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
                "[resourceId('Microsoft.Insights/components', variables('appInsightsName'))]"
            ],
            "identity": {
                "type": "SystemAssigned"
            },
            "properties": {
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
                "siteConfig": {
                    "appSettings": [
                        {
                            "name": "APPINSIGHTS_INSTRUMENTATIONKEY",
                            "value": "[reference(resourceId('Microsoft.Insights/components', variables('appInsightsName'))).InstrumentationKey]"
                        },
                        {
                            "name": "APPLICATIONINSIGHTS_CONNECTION_STRING",
                            "value": "[reference(resourceId('Microsoft.Insights/components', variables('appInsightsName'))).ConnectionString]"
                        },
                        {
                            "name": "KeyVaultUri",
                            "value": "[reference(resourceId('Microsoft.KeyVault/vaults', variables('keyVaultName'))).vaultUri]"
                        }
                    ],
                    "connectionStrings": [
                        {
                            "name": "DefaultConnection",
                            "connectionString": "[concat('Server=tcp:', reference(resourceId('Microsoft.Sql/servers', variables('sqlServerName'))).fullyQualifiedDomainName, ',1433;Database=', variables('sqlDatabaseName'), ';')]",
                            "type": "SQLAzure"
                        }
                    ]
                }
            }
        }
    ]
}

Linked Templates for Modularity

Main Template

{
    "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
    "contentVersion": "1.0.0.0",
    "parameters": {
        "templateBaseUrl": {
            "type": "string",
            "defaultValue": "https://raw.githubusercontent.com/myorg/templates/main/"
        },
        "environment": {
            "type": "string"
        }
    },
    "resources": [
        {
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2021-04-01",
            "name": "networkDeployment",
            "properties": {
                "mode": "Incremental",
                "templateLink": {
                    "uri": "[concat(parameters('templateBaseUrl'), 'modules/network.json')]",
                    "contentVersion": "1.0.0.0"
                },
                "parameters": {
                    "environment": {"value": "[parameters('environment')]"}
                }
            }
        },
        {
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2021-04-01",
            "name": "storageDeployment",
            "properties": {
                "mode": "Incremental",
                "templateLink": {
                    "uri": "[concat(parameters('templateBaseUrl'), 'modules/storage.json')]"
                },
                "parameters": {
                    "environment": {"value": "[parameters('environment')]"},
                    "subnetId": {"value": "[reference('networkDeployment').outputs.storageSubnetId.value]"}
                }
            },
            "dependsOn": ["networkDeployment"]
        },
        {
            "type": "Microsoft.Resources/deployments",
            "apiVersion": "2021-04-01",
            "name": "webAppDeployment",
            "properties": {
                "mode": "Incremental",
                "templateLink": {
                    "uri": "[concat(parameters('templateBaseUrl'), 'modules/webapp.json')]"
                },
                "parameters": {
                    "environment": {"value": "[parameters('environment')]"},
                    "subnetId": {"value": "[reference('networkDeployment').outputs.webSubnetId.value]"},
                    "storageAccountName": {"value": "[reference('storageDeployment').outputs.storageAccountName.value]"}
                }
            },
            "dependsOn": ["networkDeployment", "storageDeployment"]
        }
    ],
    "outputs": {
        "webAppUrl": {
            "type": "string",
            "value": "[reference('webAppDeployment').outputs.url.value]"
        }
    }
}

Conditional Deployments

{
    "parameters": {
        "deployDiagnostics": {
            "type": "bool",
            "defaultValue": true
        },
        "environment": {
            "type": "string"
        }
    },
    "variables": {
        "isProduction": "[equals(parameters('environment'), 'prod')]"
    },
    "resources": [
        {
            "condition": "[parameters('deployDiagnostics')]",
            "type": "Microsoft.Insights/diagnosticSettings",
            "apiVersion": "2021-05-01-preview",
            "name": "diagnostics",
            "scope": "[resourceId('Microsoft.Web/sites', variables('webAppName'))]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', variables('webAppName'))]"
            ],
            "properties": {
                "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('logAnalyticsName'))]",
                "logs": [
                    {
                        "category": "AppServiceHTTPLogs",
                        "enabled": true
                    }
                ]
            }
        },
        {
            "condition": "[variables('isProduction')]",
            "type": "Microsoft.Web/sites/slots",
            "apiVersion": "2021-02-01",
            "name": "[concat(variables('webAppName'), '/staging')]",
            "location": "[parameters('location')]",
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', variables('webAppName'))]"
            ],
            "properties": {
                "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]"
            }
        }
    ]
}

Copy Loops

{
    "parameters": {
        "storageAccounts": {
            "type": "array",
            "defaultValue": [
                {"name": "data", "sku": "Standard_LRS"},
                {"name": "logs", "sku": "Standard_GRS"},
                {"name": "backup", "sku": "Standard_RAGRS"}
            ]
        }
    },
    "resources": [
        {
            "type": "Microsoft.Storage/storageAccounts",
            "apiVersion": "2021-06-01",
            "name": "[concat(variables('prefix'), parameters('storageAccounts')[copyIndex()].name, uniqueString(resourceGroup().id))]",
            "location": "[parameters('location')]",
            "tags": "[parameters('tags')]",
            "copy": {
                "name": "storageCopy",
                "count": "[length(parameters('storageAccounts'))]",
                "mode": "Parallel"
            },
            "sku": {
                "name": "[parameters('storageAccounts')[copyIndex()].sku]"
            },
            "kind": "StorageV2",
            "properties": {
                "minimumTlsVersion": "TLS1_2",
                "supportsHttpsTrafficOnly": true,
                "allowBlobPublicAccess": false
            }
        }
    ]
}

User-Defined Functions

{
    "functions": [
        {
            "namespace": "naming",
            "members": {
                "resourceName": {
                    "parameters": [
                        {"name": "prefix", "type": "string"},
                        {"name": "resourceType", "type": "string"},
                        {"name": "environment", "type": "string"}
                    ],
                    "output": {
                        "type": "string",
                        "value": "[toLower(concat(parameters('prefix'), '-', parameters('resourceType'), '-', parameters('environment')))]"
                    }
                },
                "uniqueName": {
                    "parameters": [
                        {"name": "prefix", "type": "string"},
                        {"name": "resourceType", "type": "string"}
                    ],
                    "output": {
                        "type": "string",
                        "value": "[toLower(concat(parameters('prefix'), parameters('resourceType'), uniqueString(resourceGroup().id)))]"
                    }
                }
            }
        }
    ],
    "variables": {
        "webAppName": "[naming.resourceName('app', 'web', parameters('environment'))]",
        "storageAccountName": "[naming.uniqueName('st', 'app')]"
    }
}

Outputs for Downstream Processes

{
    "outputs": {
        "webAppName": {
            "type": "string",
            "value": "[variables('webAppName')]"
        },
        "webAppUrl": {
            "type": "string",
            "value": "[concat('https://', reference(resourceId('Microsoft.Web/sites', variables('webAppName'))).defaultHostName)]"
        },
        "webAppIdentityPrincipalId": {
            "type": "string",
            "value": "[reference(resourceId('Microsoft.Web/sites', variables('webAppName')), '2021-02-01', 'Full').identity.principalId]"
        },
        "resourceIds": {
            "type": "object",
            "value": {
                "webApp": "[resourceId('Microsoft.Web/sites', variables('webAppName'))]",
                "appServicePlan": "[resourceId('Microsoft.Web/serverfarms', variables('appServicePlanName'))]",
                "sqlServer": "[resourceId('Microsoft.Sql/servers', variables('sqlServerName'))]"
            }
        }
    }
}

Deployment Scripts

{
    "resources": [
        {
            "type": "Microsoft.Resources/deploymentScripts",
            "apiVersion": "2020-10-01",
            "name": "configureApp",
            "location": "[parameters('location')]",
            "kind": "AzurePowerShell",
            "identity": {
                "type": "UserAssigned",
                "userAssignedIdentities": {
                    "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities', variables('identityName'))]": {}
                }
            },
            "dependsOn": [
                "[resourceId('Microsoft.Web/sites', variables('webAppName'))]"
            ],
            "properties": {
                "azPowerShellVersion": "6.4",
                "timeout": "PT30M",
                "arguments": "[format('-WebAppName {0} -ResourceGroup {1}', variables('webAppName'), resourceGroup().name)]",
                "scriptContent": "
                    param([string]$WebAppName, [string]$ResourceGroup)

                    # Configure web app settings
                    $settings = @{
                        'WEBSITE_RUN_FROM_PACKAGE' = '1'
                        'WEBSITE_ENABLE_SYNC_UPDATE_SITE' = 'true'
                    }

                    Set-AzWebApp -Name $WebAppName -ResourceGroupName $ResourceGroup -AppSettings $settings

                    $output = @{
                        'configured' = $true
                        'timestamp' = (Get-Date).ToString('o')
                    }

                    $DeploymentScriptOutputs = $output
                ",
                "cleanupPreference": "OnSuccess",
                "retentionInterval": "P1D"
            }
        }
    ]
}

Best Practices Summary

  1. Use consistent naming with variables and functions
  2. Externalize parameters for environment-specific values
  3. Use secure parameters for sensitive data
  4. Modularize with linked templates for reusability
  5. Always specify API versions explicitly
  6. Add metadata and comments for documentation
  7. Use conditions for optional resources
  8. Validate templates before deployment
  9. Use What-If to preview changes
  10. Consider migrating to Bicep for improved authoring experience

Conclusion

ARM templates remain a powerful tool for Infrastructure as Code on Azure. By following these best practices, you can create maintainable, reusable templates that scale with your organization’s needs.

For new projects, consider using Bicep, which compiles to ARM templates but offers a cleaner syntax and better tooling support.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.