Nordlicht Reisen GmbH, a fictional travel agency, no longer wants to click its environments together by hand. A colleague has written a template for a storage account, and you are to read it, adapt it and deploy it. In addition, a resource group that someone created in the portal is to be captured as a template. This is exactly what the exam asks for: read, change and deploy an ARM template and export a deployment as an ARM template.
Prior knowledge. What Azure Resource Manager (ARM) is and why infrastructure as code makes sense is covered in "AZ-900 compact", lesson 3.3, and in K3, lesson 3.2.1. K3 teaches the Bicep language, modules and what-if in module 3.2 (lessons 3.2.2 to 3.2.4); lesson 3.2 of this course only adds what the exam requires about Bicep. This lesson is about the JSON format behind both.
The structure of an ARM template
An ARM template is a JSON file. In its simplest form it consists of these elements (the table follows Microsoft's description):
| Element | Required | What for |
|---|---|---|
$schema |
yes | address of the JSON schema that describes the version of the template language; it depends on the scope of the deployment |
contentVersion |
yes | version of the template, for example 1.0.0.0; any value, for keeping track of important changes |
parameters |
no | values that you provide at deployment |
variables |
no | values that you reuse in the template to shorten expressions |
functions |
no | custom functions |
resources |
yes | the resource types that are deployed or updated |
outputs |
no | values that come back after the deployment |
In addition there are the elements languageVersion, apiProfile and definitions, which play no role in this lesson. Important to know: With "languageVersion": "2.0", resources becomes an object instead of a list, and resources then have a symbolic name. This also happens when a Bicep file uses certain features; you only have to be able to recognize it.
A small, complete template that creates a storage account (Microsoft shows it in this form in the documentation on template specs):
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountName": {
"type": "string",
"minLength": 3,
"maxLength": 24
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]"
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2025-06-01",
"name": "[parameters('storageAccountName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_LRS"
},
"kind": "StorageV2",
"properties": {
"accessTier": "Hot"
}
}
]
}
This is how you read it:
- Parameters have a
type(string,securestring,int,bool,object,secureObject,array) and optionallydefaultValue,allowedValues,minLength,maxLength,minValue,maxValueandmetadatawith adescription. Here the name must be 3 to 24 characters long, andlocationtakes the location of the resource group if nobody provides anything. - Expressions are in square brackets and in quotation marks:
"[parameters('location')]"reads a parameter,[resourceGroup().location]calls a function. - Resources have
type(provider and resource type, hereMicrosoft.Storage/storageAccounts),apiVersion(the version of the REST interface used to create the resource),name, usuallylocation, and depending on the typesku,kindandproperties.dependsOnsets an order; resources without a dependency are deployed in parallel. Microsoft advises: For a new template, use the current API version, and change it later only when you need a new feature. This lowers the risk that a new version changes the behavior. - Outputs return values. An output of type
securestringdoesn't appear in the deployment history. - Comments are possible in templates with
//and/* ... */. For the Azure CLI, Microsoft names the switch--handle-extended-json-formatfor this, which the reference foraz deployment group createnow marks as deprecated. According to Microsoft, multiline strings in templates work only with Azure PowerShell or Azure CLI, not through the portal, a DevOps pipeline or the REST interface.
The limits from the documentation: at most 256 parameters, 256 variables, 800 resources and 64 outputs per template.