All the example codes are available in GitHub.
Background
Governance of cloud estates is challenging for businesses. It’s crucial to enforce security policies, workload redundancies, uniformity (such as naming conventions), simplify deployments with packaged artifacts (i.e., ARM templates), Azure role-based access control (Azure RBAC) across the enterprise.
Generally, the idea is, a centralized team (sometimes referred as platform team) builds and publishes Infrastructure-as-code artifacts and number of product development teams consume them, only providing their own parameters.

Azure offers native capabilities like Azure Policy, Blueprints and Management groups to address this problem. But there are wide range of external solutions (Terraform, Pulumi etc.) available too.
One attribute of Terraform, strikes me a lot is the ability to store a versioned module in a registry and consume it from the registry. The same principle that familiar to engineers and widely used in programming languages – such as NuGet for .net, Maven for Java, npm for Node
With ARM templates it’s rather unpleasant. If you currently have your templates in an Azure repo, GitHub repo or storage account, you run into several challenges when trying to share and use the templates. For a user to deploy it, the template must either be local or the URL for the template must be publicly accessible. To get around this limitation, you might share copies of the template with users who need to deploy it, or open access to the repo or storage account. When users own local copies of a template, these copies can eventually diverge from the original template. When you make a repo or storage account publicly accessible, you may allow unintended users to access the template.
Azure Resource Manager – Template Spec
Microsoft delivered some cool new features for Resource manager templates recently. One of these features, named as Template Spec. Template Spec is a first-class Azure Resource type, but it really is just a regular ARM template. Best part is that you can version it, persist it in Azure – just like a Terraform registry, share it across the organization with RBAC and consume them from repository.
Template Specs is currently in preview. To use it, you must install the latest version of PowerShell or Azure CLI. For Azure PowerShell, use version 5.0.0 or later. For Azure CLI, use version 2.14.2 or later.
The benefit of using template specs is that you can create canonical templates and share them with teams in your organization. The template specs are secure because they’re available to Azure Resource Manager for deployment, but not accessible to users without Azure RBAC permission. Users only need read access to the template spec to deploy its template, so you can share the template without allowing others to modify it.
The templates you include in a template spec should be verified by the platform team (or administrators) in your organization to follow the organization’s requirements and guidance.
How template spec works?
If you are familiar with ARM template, template specs are not new to you. They are just typical ARM templates and stored in Azure Resource group as “template spec” with a version number. That means, you can take any ARM template (the template JSON file only – without any parameter files) and publish it as template spec, using PowerShell, Azure CLI or REST API.
Publishing Template Spec
Let’s say I have a template that defines just an Application Insight component.
{
"contentVersion": "1.0.0.0",
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"parameters": {
"appInsights": { "type": "string" },
"location": { "type": "string" }
},
"resources": [{
"type": "microsoft.insights/components",
"apiVersion": "2020-02-02-preview",
"name": "[parameters('appInsights')]",
"location": "[parameters('location')]",
"properties": {
"ApplicationId": "[parameters('appInsights')]",
"Application_Type": "web"
}
}
],
"outputs": {
"instrumentationKey": {
"type": "string",
"value": "[reference(parameters('appInsights')).InstrumentationKey]"
}
}
}
We can now publish this as “template spec” using Azure CLI:
az ts create \
--name "cloudoven-appInsights" \
--version $VERSION \
--resource-group $RESOURCE_GROUP \
--location $LOCATION \
--template-file "component.json" \
--yes --query 'id' -o json
Once published, you can see it in Azure Portal – appeared as a new resource type Microsoft.Resources/templateSpecs
.

Consuming Template Spec
Every published Template Spec has a unique ID. To consume a Template Spec, all you need is the ID of the Template Spec. You can retrieve the ID in Azure CLI:
APPINS_TSID=$(az ts show --resource-group $TSRGP --name $TSNAME --version $VERSION --query 'id' -o json)
echo Template Spec ID: $APPINS_TSID
You can now, deploy Azure Resources with the Template Spec ID and optionally your own parameters, like following:
az deployment group create \
--resource-group $RESOURCEGROUP \
--template-spec $APPINS_TSID \
--parameters "parameters.json
Linked Templates & Modularizations
Overtime, Infrastructure-as-code tends to become big monolithic file containing numerous resources. ARM templates (thanks to all it’s verbosity) specially known for growing big fast and becomes difficult to comprehend by reading a large JSON file. You could address the issue before by using linked templates. However, with a caveat that linked templates needed to be accessible via an URL in the public internet – far from ideal.
Good news is Template Spec got this covered. If the main template for your template spec references linked templates, the PowerShell and CLI commands can automatically find and package the linked templates from your local drive.
Example
Here I have an example ARM template that defines multiple resources (Application Insights, Server farm and a web app) in small files and finally creating a main template that brings everything together. One can then publish the main template as Template Spec – hence any consumer can provision their web app just by pointing to the ID of the template spec. Here’s the interesting bit of the main template:
"resources": [
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2020-06-01",
"name": "DeployAppInsights",
"properties": {
"mode": "Incremental",
"parameters": {
"appInsights": { "value": "[parameters('appInsights')]"},
"location": {"value": "[parameters('location')]"}
},
"templateLink": {
"relativePath": "../appInsights/component.json"
}
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2020-06-01",
"name": "DeployHostingplan",
"properties": {
"mode": "Incremental",
"templateLink": {
"relativePath": "../server-farm/component.json"
}
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2020-06-01",
"name": "DeployWebApp",
"dependsOn": [ "DeployHostingplan" ],
"properties": {
"mode": "Incremental",
"templateLink": {
"relativePath": "../web-app/component.json"
}
}
}
]
You see, Template Specs natively offers the modularity, centralized registry, however, they are still ARM JSON files. One common criticism of ARM template is it’s too verbose and JSON are not particularly famous for readability.
Microsoft is aiming to address these concerns with a new Domain Specific Language (DSL) that named a Azure Bicep.
What is Bicep?
Bicep aims to drastically simplify the authoring experience with a cleaner syntax and better support for modularity and code re-use. Bicep is a transparent abstraction over ARM and ARM templates, which means anything that can be done in an ARM Template can be done in bicep (outside of temporary known limitations).
If we take the same Application Insight component (above) and re-write it in Bicep, it looks following:
param appInsights string
param location string = resourceGroup().location
resource appIns 'Microsoft.Insights/components@2020-02-02-preview' = {
name: appInsights
location: location
kind: appInsights
properties: {
Application_Type: 'web'
}
}
output InstrumentationKey string = appIns.properties.InstrumentationKey
Very clean and concise compared to the ARM JSON version of it. If you are coming from Terraform, you might already find yourself at home – because Bicep took lot of inspiration from Terraform HCL (HashiCorp Language). You save bicep scripts with .bicep
file extensions.
Important things to understand, Bicep is a client-side language layer sits on top of ARM json. The idea is you write it in Bicep then compile the script using a Bicep compiler (or Transpiler) to produce ARM JSON as compiled artifact and you still deploy ARM template (JSON) to Azure. Here’s how you compile a bicep file to produce the ARM JSON:
bicep build ./main.bicep
Bicep currently is in experimental state, and not recommended to use in production.
Creating Template Spec in Bicep
The above Example – you’ve seen how you could create a Template Spec that is quite modularized into linked templates. Let’s rewrite that in Bicep and see how clean and simple it looks:
param webAppName string = ''
param appInsights string = ''
param location string = resourceGroup().location
param hostingPlanName string = ''
param containerSpec string =''
param costCenter string
param environment string
module appInsightsDeployment '../appinsights/component.bicep' = {
name: 'appInsightsDeployment'
params:{
appInsights: '${appInsights}'
location: '${location}'
costCenter: costCenter
environment: environment
}
}
module deployHostingplan '../server-farm/component.bicep' = {
name: 'deployHostingplan'
params:{
hostingPlanName: '${hostingPlanName}'
location: '${location}'
costCenter: costCenter
environment: environment
}
}
module deployWebApp '../web-app/component.bicep' = {
name: 'deployWebApp'
params:{
location: '${location}'
webAppName: '${webAppName}'
instrumentationKey: appInsightsDeployment.outputs.InstrumentationKey
serverFarmId: deployHostingplan.outputs.hostingPlanId
containerSpec: '${containerSpec}'
costCenter: costCenter
environment: environment
}
}
Notice, Bicep came up with a nice keyword module
to address the linked template scenario. Bicep module is an opaque set of one or more resources to be deployed together. It only exposes parameters and outputs as contract to other Bicep files, hiding details on how internal resources are defined. This allows you to abstract away complex details of the raw resource declaration from the end user who now only needs to be concerned about the module contract. Parameters and outputs are optional.
CI/CD – GitHub Action
Let’s create a delivery pipeline in GitHub action to compile our Bicep file and publish them as Template Spec in Azure. Following GitHub workflow, installs Bicep tools, compiles the scripts, and finally publishes them as Template Spec in Azure. You can see the complete repository in GitHub.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Bicep CLI
working-directory: ./src/template-spec-bicep/paas-components
run: |
chmod +x ./install-bicep.sh
./install-bicep.sh
- name: Compile Bicep Scripts
working-directory: ./src/template-spec-bicep/paas-components
run: |
chmod +x ./build-templates.sh
./build-templates.sh
- name: Azure Login
uses: Azure/login@v1.1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Deploy Template Specs to Azure
working-directory: ./src/template-spec-bicep/paas-components
run: |
chmod +x ./deploy-templates.sh
./deploy-templates.sh
Consuming the template spec doesn’t change disregarding the choice you make with Bicep or ARM template. Consumers just create a parameter file and deploy resource only specifying the Template Spec ID. You can see an example of Consumer workflow (also GitHub action) here.
Conclusion
I am excited how the Azure team is offering new tools like Bicep, Template Specs to simplify the cloud governance, self-service areas. It’s important to understand that Bicep team is not competing with available tools in the space (like Terraform etc.) rather offering more options to folks in their cloud journey.
Bicep is in experimental phase now and Template Specs are not in preview, therefore, don’t use them in production just yet.
Hi Moim, How are you packaging and shipping the IaC packages? Are they ARM/terraform/bicep modules within nuget packages? How are product teams consuming them?
LikeLike
Hi Dushyant, the packages are stored natively by Azure -Template Spec. Check out Template Spec in Microsoft docs to understand that more. Once you publish, “packages” (i.e. template specs) are natively stored by Azure and versioned. You can apply RBAC to them as well. And people who has access to them, can deploy/consume as described in the article.
LikeLike
Thanks for your response Moim. Template spec is exciting. And thank you for a helpful post.
LikeLike