The goal
You've been creating resources by hand in the portal and want a repeatable way to do it. Bicep is Azure's template language: you describe what should exist, and Azure Resource Manager works out how to create it. By the end of this post you'll have a storage account deployed from a file you can keep in Git.
Step 1: install and check the tools
az --version
az bicep install
az bicep version
az login
az account show --query "{name:name, id:id}" -o table
Check the subscription name carefully. Deploying to the wrong one is the easiest mistake to make. Switch it with az account set --subscription "<name-or-id>".
Create a resource group to deploy into:
az group create --name rg-bicep-demo --location centralindia
Step 2: write the template
Save this as main.bicep:
@description('Globally unique name: 3-24 lowercase letters and numbers')
@minLength(3)
@maxLength(24)
param storageName string
param location string = resourceGroup().location
@allowed(['Standard_LRS', 'Standard_ZRS'])
param sku string = 'Standard_LRS'
resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageName
location: location
sku: {
name: sku
}
kind: 'StorageV2'
properties: {
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
supportsHttpsTrafficOnly: true
}
}
output blobEndpoint string = storage.properties.primaryEndpoints.blob
The parts you just used:
| Keyword | Meaning |
|---|---|
param | A value supplied at deployment time, optionally with a default |
resource | Something to create, with its type and API version |
output | A value returned after deployment |
@allowed, @minLength | Rules checked before anything is deployed |
Step 3: add a parameter file
Keep per-environment values out of the template. Save this as dev.bicepparam beside it:
using './main.bicep'
param storageName = 'stbicepdemo2611'
param sku = 'Standard_LRS'
Choose your own storage name, because it must be unique across Azure.
Step 4: validate, then preview
Validation catches bad values and syntax:
az deployment group validate \
--resource-group rg-bicep-demo \
--template-file main.bicep \
--parameters dev.bicepparam
What-if shows what would change, without changing anything:
az deployment group what-if \
--resource-group rg-bicep-demo \
--template-file main.bicep \
--parameters dev.bicepparam
Read the symbols in the output:
| Symbol | Meaning |
|---|---|
+ | Will be created |
~ | Will be modified |
- | Will be deleted |
* | Ignored by this deployment |
= | No change |
Tip
What-if can report some properties as changing when they won't really change. Treat it as a review aid, and look closely at anything marked
-or any unexpected~.
Step 5: deploy
az deployment group create \
--name first-storage \
--resource-group rg-bicep-demo \
--template-file main.bicep \
--parameters dev.bicepparam
az deployment group show \
--name first-storage \
--resource-group rg-bicep-demo \
--query properties.outputs
Run the same command again. Nothing breaks, because Bicep describes the desired state rather than a list of steps. That's why a template is safer to repeat than a script.
When the deployment fails
List the failed operations to get the real error:
az deployment operation group list \
--name first-storage \
--resource-group rg-bicep-demo \
--query "[?properties.provisioningState=='Failed'].properties.statusMessage" -o json
| Error | Meaning | Fix |
|---|---|---|
StorageAccountAlreadyTaken | The name is used elsewhere in Azure | Pick another name |
AccountNameInvalid | The name has capitals or symbols | Use lowercase letters and numbers |
RequestDisallowedByPolicy | An Azure Policy blocked it | Read the policy name in the error |
AuthorizationFailed | Your role can't create this resource | Ask for the role on this scope |
InvalidTemplate | The file has an error | Run az bicep build --file main.bicep |
The find the real deployment error cheat sheet covers more of these.
Caution
The default deployment mode is Incremental, which leaves resources not named in the template alone. Don't use
--mode Completewhile you're learning: it deletes resources in the group that aren't in the template.
Clean up
az group delete --name rg-bicep-demo --yes
This deletes everything in the group, so double-check the name first.
Prevent it next time
- Keep templates in Git and review the what-if output in the pull request.
- Use one parameter file per environment rather than copying the template.
- Run
az bicep decompile --file exported.jsonto get a starting point from an exported ARM template; expect to tidy up the result. - Once you're comfortable, read what-if and deployment stacks.