This guide shows how to create, manage, and deploy Template Specs with Bicep files for enterprise-scale infrastructure deployment.
Template Specs provide a centralized way to share Bicep-defined infrastructure as versioned, reusable artifacts without exposing the underlying code. This approach enables organizations to create governed, standardized infrastructure components that can be deployed by teams without requiring direct access to modify the templates.
What's New in 2025
Native Bicep support in Azure Portal for Template Specs
Enhanced versioning with semantic versioning support
Deployment history tracking and rollback capabilities
Integration with Azure RBAC for fine-grained access control
Built-in validation for compliance and governance
Creating a Template Spec
You can create Template Specs using PowerShell, Azure CLI, or directly with Bicep.
Using PowerShell
# Create a resource group for storing Template Specs
New-AzResourceGroup `
-Name templateSpecRG `
-Location westus2
# Create a Template Spec from a Bicep file
New-AzTemplateSpec `
-Name storageSpec `
-Version "1.0.0" `
-ResourceGroupName templateSpecRG `
-Location westus2 `
-TemplateFile "path/to/storage.bicep" `
-DisplayName "Standard Storage Account" `
-Description "Deploys a storage account with configurable performance tier and redundancy"
Using Azure CLI
# Create a resource group for storing Template Specs
az group create \
--name templateSpecRG \
--location westus2
# Create a Template Spec from a Bicep file
az ts create \
--name storageSpec \
--version "1.0.0" \
--resource-group templateSpecRG \
--location westus2 \
--template-file "path/to/storage.bicep" \
--display-name "Standard Storage Account" \
--description "Deploys a storage account with configurable performance tier and redundancy"
Using Bicep (Infrastructure as Code approach)
Create a Bicep file (template-spec.bicep) to define and deploy your Template Spec:
# Create a resource group for deployment
New-AzResourceGroup `
-Name storageRG `
-Location westus2
# Get the Template Spec ID
$id = (Get-AzTemplateSpec `
-ResourceGroupName templateSpecRG `
-Name storageSpec `
-Version "1.0.0").Versions.Id
# Deploy the Template Spec
New-AzResourceGroupDeployment `
-TemplateSpecId $id `
-ResourceGroupName storageRG `
-storageNamePrefix "finance" `
-storageAccountType "Standard_GRS" `
-enableHns $true
Azure CLI Deployment
# Create a resource group for deployment
az group create \
--name storageRG \
--location westus2
# Get the Template Spec ID
templateSpecId=$(az ts show \
--name storageSpec \
--resource-group templateSpecRG \
--version "1.0.0" \
--query "id" -o tsv)
# Deploy the Template Spec
az deployment group create \
--resource-group storageRG \
--template-spec $templateSpecId \
--parameters storageNamePrefix=finance storageAccountType=Standard_GRS enableHns=true
Bicep Deployment
For a more declarative approach, reference the Template Spec from your Bicep files:
As your templates evolve, create new versions to maintain backward compatibility:
# PowerShell - Create a new version
New-AzTemplateSpec `
-Name storageSpec `
-Version "2.0.0" `
-ResourceGroupName templateSpecRG `
-Location westus2 `
-TemplateFile "path/to/updated-storage.bicep"
# Azure CLI - Create a new version
az ts create \
--name storageSpec \
--version "2.0.0" \
--resource-group templateSpecRG \
--location westus2 \
--template-file "path/to/updated-storage.bicep"
Listing Available Template Specs
# PowerShell - List all Template Specs
Get-AzTemplateSpec -ResourceGroupName templateSpecRG
# PowerShell - List all versions of a Template Spec
Get-AzTemplateSpec -ResourceGroupName templateSpecRG -Name storageSpec
# Azure CLI - List all Template Specs
az ts list --resource-group templateSpecRG -o table
# Azure CLI - List all versions of a Template Spec
az ts list --resource-group templateSpecRG --name storageSpec -o table
Removing Template Specs
# PowerShell - Remove a specific version
Remove-AzTemplateSpec `
-ResourceGroupName templateSpecRG `
-Name storageSpec `
-Version "1.0.0"
# PowerShell - Remove all versions of a Template Spec
Remove-AzTemplateSpec `
-ResourceGroupName templateSpecRG `
-Name storageSpec
# Azure CLI - Remove a specific version
az ts delete \
--name storageSpec \
--version "1.0.0" \
--resource-group templateSpecRG
# Azure CLI - Remove all versions of a Template Spec
az ts delete \
--name storageSpec \
--resource-group templateSpecRG \
--yes
CI/CD Integration
Azure DevOps Pipeline Integration
# azure-pipelines.yml - Create and update Template Specs
trigger:
branches:
include:
- main
paths:
include:
- 'templates/**'
pool:
vmImage: 'ubuntu-latest'
variables:
templateSpecName: 'storageSpec'
templateSpecRG: 'templateSpecRG'
templateFile: '$(Build.SourcesDirectory)/templates/storage.bicep'
steps:
- task: AzureCLI@2
displayName: 'Create/Update Template Spec'
inputs:
azureSubscription: 'your-azure-service-connection'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
# Get current date for version
CURRENT_DATE=$(date +"%Y%m%d%H")
VERSION="1.0.$CURRENT_DATE"
echo "Creating Template Spec version: $VERSION"
az ts create \
--name $(templateSpecName) \
--version $VERSION \
--resource-group $(templateSpecRG) \
--location westus2 \
--template-file "$(templateFile)" \
--display-name "Storage Account (CI/CD)" \
--description "Auto-generated from build pipeline"
GitHub Actions Workflow
# .github/workflows/template-spec.yml
name: Update Template Specs
on:
push:
branches: [ main ]
paths:
- 'templates/**'
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Azure Login
uses: azure/login@v1
with:
creds: ${{ secrets.AZURE_CREDENTIALS }}
- name: Create/Update Template Spec
run: |
# Generate version based on date and commit hash
VERSION="1.0.$(date +%Y%m%d)-${GITHUB_SHA::7}"
echo "Creating Template Spec version: $VERSION"
az ts create \
--name storageSpec \
--version $VERSION \
--resource-group templateSpecRG \
--location westus2 \
--template-file "./templates/storage.bicep" \
--display-name "Storage Account (GitHub)" \
--description "Created from GitHub Actions workflow"
Best Practices for Template Specs
1. Implement a Versioning Strategy
Use semantic versioning (MAJOR.MINOR.PATCH) for Template Spec versions:
Increment MAJOR for breaking changes
Increment MINOR for new features (backward compatible)
Increment PATCH for bug fixes
2. Organize Template Specs by Domain
Group related Template Specs in the same resource group by domain or application:
network-templatespecs-rg - For networking components
compute-templatespecs-rg - For compute resources
data-templatespecs-rg - For data services
3. Document Parameters and Outputs
Include comprehensive metadata for all parameters:
@description('The SKU tier for the storage account')
@allowed([
'Standard_LRS'
'Standard_GRS'
'Standard_RAGRS'
])
param storageSku string = 'Standard_LRS'
4. Implement Automated Testing
Test your Template Specs as part of CI/CD:
Validate syntax before publishing
Deploy to test environments
Run what-if operations to verify expected changes
5. Access Control
Grant users the minimum permissions needed:
Template Spec Reader - For deployments only
Template Spec Contributor - For creating/updating Template Specs
# Grant user permission to deploy from Template Spec
az role assignment create \
--assignee user@example.com \
--role "Template Spec Reader" \
--scope "/subscriptions/{subscriptionId}/resourceGroups/templateSpecRG"
Conclusion
Template Specs provide a powerful way to standardize infrastructure deployments across an organization while maintaining control over the templates themselves. By combining Bicep's declarative syntax with Template Specs' version management, you can create reusable infrastructure components that adhere to organizational standards and compliance requirements.