ARM Templates & Bicep
SkillCloud & infraYour AI can deploy and manage resources in Microsoft's Azure cloud using ARM templates and Bicep files. Once this skill is added, it can create modular deployments and manage dependencies between the resources it deploys. It is designed for Azure-native infrastructure as code work.
Available today. Use it from your connected AI after setup.
No other account needed.
After adding the skill, ask your AI to deploy or update Azure resources using one of your ARM templates or Bicep files.
Then ask your AI: use the ARM Templates & Bicep skill
What your AI can do with it
- Deploy Azure resources with ARM templates
- Deploy Azure resources with Bicep files
- Manage Azure resources
- Create modular deployments
- Manage dependencies between resources
What this skill tells your AI
The instructions your AI receives, as published by bagelhole/devops-security-agent-skills in infrastructure/cloud-azure/arm-templates/SKILL.md and read by ahel’s review.
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
# 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
// 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
// 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
// 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
// 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
{
"$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
# 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
// 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
// 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.
Signals
- GitHub stars
- 1k
- Forks
- 157
- Last commit
- May 2026
Advanced
- Catalog kind
- skill
- Gateway key
arm-templates- Source
- github.com/bagelhole/devops-security-agent-skills