How to use Azure Verified Modules (AVM) for Bicep?
Azure Azure Landing Zones Infrastructure-as-code Azure Bicep 💪
Table of contents:
Building infrastructure-as-code from scratch can be time-consuming and error-prone. In a previous article, I covered how to govern Azure resources with Template Specs. Today, I want to introduce you to Azure Verified Modules (AVM) — Microsoft’s official library of pre-built, tested, and supported Bicep (and Terraform) modules that follow best practices out of the box.
What are Azure Verified Modules? #
Azure Verified Modules (AVM) is an initiative by Microsoft to provide a single source of truth for Infrastructure-as-Code modules. These modules are:
- Verified: Tested and validated by Microsoft
- Supported: Backed by Microsoft and the community
- Consistent: Follow the same patterns and conventions
- Secure: Implement security best practices by default
- Well-documented: Include comprehensive documentation and examples
The AVM initiative consolidates and supersedes previous efforts like CARML (Common Azure Resource Modules Library) and TFVM (Terraform Verified Modules).
Module Types #
AVM provides two types of modules:
Resource Modules #
Resource modules deploy a single Azure resource with all its extensions and child resources. For example, a Storage Account resource module would include:
- The storage account itself
- Blob containers
- File shares
- Private endpoints
- Diagnostic settings
- Role assignments
Pattern Modules #
Pattern modules combine multiple resource modules to deploy a complete solution or architecture pattern. Examples include:
- Hub-spoke network topology
- Azure Kubernetes Service with supporting infrastructure
- Azure Landing Zone components
Getting Started with AVM #
Browsing Available Modules #
Visit the AVM Module Index to explore all available Bicep modules. Each module includes:
- Version history
- Input parameters
- Outputs
- Usage examples
- Source code link
Using Modules from the Public Registry #
AVM modules are published to the Microsoft Public Bicep Registry. To use them, reference the module directly in your Bicep file:
1module storageAccount 'br/public:avm/res/storage/storage-account:0.14.0' = {
2 name: 'storageAccountDeployment'
3 params: {
4 name: 'stweekendsprints001'
5 location: location
6 skuName: 'Standard_LRS'
7 kind: 'StorageV2'
8 }
9}
The syntax br/public:avm/res/storage/storage-account:0.14.0 breaks down as:
br/public- Bicep Registry (public)avm/res/storage/storage-account- Module path0.14.0- Semantic version
Configuring bicepconfig.json #
To simplify module references, configure your bicepconfig.json:
1{
2 "moduleAliases": {
3 "br": {
4 "public": {
5 "registry": "mcr.microsoft.com",
6 "modulePath": "bicep"
7 }
8 }
9 }
10}
Practical Example: Deploying a Storage Account with AVM #
Let’s deploy a production-ready storage account with private endpoint and diagnostic settings:
1targetScope = 'resourceGroup'
2
3@description('Location for all resources')
4param location string = resourceGroup().location
5
6@description('Environment name')
7@allowed(['dev', 'test', 'prod'])
8param environment string = 'dev'
9
10@description('Log Analytics Workspace Resource ID for diagnostics')
11param logAnalyticsWorkspaceId string
12
13@description('Virtual Network Resource ID for private endpoint')
14param virtualNetworkResourceId string
15
16@description('Subnet Resource ID for private endpoint')
17param privateEndpointSubnetResourceId string
18
19var storageAccountName = 'st${environment}weekendsprints'
20
21module storageAccount 'br/public:avm/res/storage/storage-account:0.14.0' = {
22 name: 'storageAccountDeployment'
23 params: {
24 name: storageAccountName
25 location: location
26 skuName: environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'
27 kind: 'StorageV2'
28
29 // Security settings
30 allowBlobPublicAccess: false
31 allowSharedKeyAccess: false
32 minimumTlsVersion: 'TLS1_2'
33 supportsHttpsTrafficOnly: true
34
35 // Network settings
36 publicNetworkAccess: 'Disabled'
37 networkAcls: {
38 defaultAction: 'Deny'
39 bypass: 'AzureServices'
40 }
41
42 // Private endpoint
43 privateEndpoints: [
44 {
45 subnetResourceId: privateEndpointSubnetResourceId
46 service: 'blob'
47 privateDnsZoneResourceIds: [
48 privateDnsZone.outputs.resourceId
49 ]
50 }
51 ]
52
53 // Blob containers
54 blobServices: {
55 containers: [
56 {
57 name: 'data'
58 publicAccess: 'None'
59 }
60 {
61 name: 'logs'
62 publicAccess: 'None'
63 }
64 ]
65 }
66
67 // Diagnostics
68 diagnosticSettings: [
69 {
70 workspaceResourceId: logAnalyticsWorkspaceId
71 metricCategories: [
72 {
73 category: 'AllMetrics'
74 }
75 ]
76 }
77 ]
78
79 // Tags
80 tags: {
81 Environment: environment
82 ManagedBy: 'Bicep-AVM'
83 }
84 }
85}
86
87// Private DNS Zone for blob storage
88module privateDnsZone 'br/public:avm/res/network/private-dns-zone:0.5.0' = {
89 name: 'privateDnsZoneDeployment'
90 params: {
91 name: 'privatelink.blob.${az.environment().suffixes.storage}'
92 location: 'global'
93 virtualNetworkLinks: [
94 {
95 virtualNetworkResourceId: virtualNetworkResourceId
96 registrationEnabled: false
97 }
98 ]
99 }
100}
101
102output storageAccountResourceId string = storageAccount.outputs.resourceId
103output storageAccountName string = storageAccount.outputs.name
104output blobEndpoint string = storageAccount.outputs.primaryBlobEndpoint
Notice how much functionality we get with minimal code! The AVM module handles:
- Resource creation with secure defaults
- Private endpoint configuration
- DNS zone integration
- Diagnostic settings
- Role assignments (if needed)
AVM vs Template Specs #
In my Template Specs article, I showed how to share templates within an organization. Here’s how AVM compares:
| Aspect | Template Specs | AVM |
|---|---|---|
| Scope | Organization-specific | Community/Microsoft |
| Maintenance | Your team | Microsoft + Community |
| Customization | Full control | Limited to parameters |
| Updates | Manual | Semantic versioning |
| Best practices | Your standards | Microsoft standards |
My recommendation: Use AVM modules as your foundation and wrap them with Template Specs when you need organization-specific customizations or governance.
Integrating AVM with CI/CD #
GitLab CI/CD #
Building on my GitLab CI/CD Components article, here’s how to validate and deploy AVM-based templates:
1include:
2 - component: $CI_SERVER_FQDN/gitlab-cicd-component/gitlab-cicd-component-azure-bicep/azure-bicep@0.0.1
3 inputs:
4 template_file: main.bicep
5 parameters_file: parameters.prod.json
6 resource_group: rg-weekendsprints-prod
7 location: westeurope
Azure DevOps #
1trigger:
2 branches:
3 include:
4 - main
5 paths:
6 include:
7 - infra/**
8
9pool:
10 vmImage: 'ubuntu-latest'
11
12variables:
13 azureServiceConnection: 'WeekendSprints-SPN'
14 resourceGroupName: 'rg-weekendsprints-prod'
15 location: 'westeurope'
16
17stages:
18 - stage: Validate
19 jobs:
20 - job: ValidateBicep
21 steps:
22 - task: AzureCLI@2
23 displayName: 'Bicep Build & Lint'
24 inputs:
25 azureSubscription: $(azureServiceConnection)
26 scriptType: 'bash'
27 scriptLocation: 'inlineScript'
28 inlineScript: |
29 az bicep build --file infra/main.bicep
30
31 - task: AzureCLI@2
32 displayName: 'What-If Analysis'
33 inputs:
34 azureSubscription: $(azureServiceConnection)
35 scriptType: 'bash'
36 scriptLocation: 'inlineScript'
37 inlineScript: |
38 az deployment group what-if \
39 --resource-group $(resourceGroupName) \
40 --template-file infra/main.bicep \
41 --parameters infra/parameters.prod.json
42
43 - stage: Deploy
44 dependsOn: Validate
45 condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
46 jobs:
47 - deployment: DeployInfra
48 environment: 'production'
49 strategy:
50 runOnce:
51 deploy:
52 steps:
53 - checkout: self
54 - task: AzureCLI@2
55 displayName: 'Deploy Bicep'
56 inputs:
57 azureSubscription: $(azureServiceConnection)
58 scriptType: 'bash'
59 scriptLocation: 'inlineScript'
60 inlineScript: |
61 az deployment group create \
62 --resource-group $(resourceGroupName) \
63 --template-file infra/main.bicep \
64 --parameters infra/parameters.prod.json
Version Pinning Strategy #
AVM uses semantic versioning. Here’s my recommended approach:
1// ✅ Good: Pin to specific version
2module storage 'br/public:avm/res/storage/storage-account:0.14.0' = { }
3
4// ⚠️ Caution: Minor version range (may introduce new features)
5module storage 'br/public:avm/res/storage/storage-account:0.14' = { }
6
7// ❌ Avoid: Major version only (breaking changes possible)
8module storage 'br/public:avm/res/storage/storage-account:0' = { }
9```$$
10
11For production workloads, always pin to specific versions and test updates in non-production environments first.
12
13## Contributing to AVM
14
15AVM is open source! You can contribute by:
16
171. **Reporting issues**: Found a bug? [Open an issue](https://aka.ms/AVM/BicepResourceModuleIssue)
182. **Suggesting features**: Have an idea? Start a discussion
193. **Contributing code**: Follow the [contribution guide](https://azure.github.io/Azure-Verified-Modules/contributing/bicep/)
20
21## Resources
22
23- [AVM Documentation](https://aka.ms/AVM)
24- [Bicep Module Index](https://azure.github.io/Azure-Verified-Modules/indexes/bicep/)
25- [Public Bicep Registry](https://github.com/Azure/bicep-registry-modules)
26- [AVM GitHub Organization](https://github.com/Azure/Azure-Verified-Modules)
27
28## Conclusion
29
30Azure Verified Modules represent a significant step forward in Infrastructure-as-Code maturity. By leveraging these pre-built, tested modules, you can:
31
32- Accelerate deployments with battle-tested code
33- Implement security best practices by default
34- Reduce maintenance burden on your team
35- Focus on business logic rather than boilerplate
36
37Start exploring the [AVM catalog](https://azure.github.io/Azure-Verified-Modules/indexes/bicep/) today and level up your Bicep deployments! 💪
38
39---