12 — Azure Verified Modules: swap one and compare¶
Everything so far was hand-written, which is right for learning and wrong for production. This chapter replaces one module with the Microsoft-maintained equivalent and looks at what changes.
Time: 30 minutes · Cost: none beyond a redeploy
12.1 What AVM is¶
Azure Verified Modules are Microsoft's official Bicep (and Terraform) module library: maintained, versioned, tested against the Well-Architected Framework, and published to the Microsoft Container Registry. They come in two shapes:
| Kind | Path | Scope |
|---|---|---|
| Resource module | br/public:avm/res/<provider>/<resource>:<version> |
one resource type and its children |
| Pattern module | br/public:avm/ptn/<area>/<pattern>:<version> |
an architecture composed of several |
Find and pin versions from the registry itself:
curl -s https://mcr.microsoft.com/v2/bicep/avm/res/storage/storage-account/tags/list | python -m json.tool
At the time of writing the latest is 0.33.0. Always check — AVM moves quickly, and a version is a contract you should choose deliberately rather than inherit.
The registry alias is already configured in bicepconfig.json:
"moduleAliases": {
"br": { "avm": { "registry": "mcr.microsoft.com", "modulePath": "bicep/avm/res" } }
}
so br/public:avm/res/storage/storage-account:0.33.0 and br:avm/storage/storage-account:0.33.0
both work.
12.2 The swap¶
Replace the storage module in main.bicep:
module storage 'modules/platform/storage.bicep' = {
scope: platformResourceGroup
name: 'platform-storage'
params: {
name: globalName('st', workload, environment, subscription().id)
location: location
tags: tags
privateEndpointSubnetId: spoke.outputs.privateEndpointSubnetId
privateDnsZoneId: privateDnsZones[blobZoneIndex].outputs.id
workspaceId: monitoring.outputs.id
}
}
with:
module storage 'br/public:avm/res/storage/storage-account:0.33.0' = {
scope: platformResourceGroup
name: 'platform-storage'
params: {
name: globalName('st', workload, environment, subscription().id)
location: location
tags: tags
skuName: 'Standard_LRS'
kind: 'StorageV2'
allowBlobPublicAccess: false
allowSharedKeyAccess: false
minimumTlsVersion: 'TLS1_2'
publicNetworkAccess: 'Disabled'
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
}
blobServices: {
containers: [ { name: 'app-data' } ]
deleteRetentionPolicyEnabled: true
deleteRetentionPolicyDays: 7
}
privateEndpoints: [
{
service: 'blob'
subnetResourceId: spoke.outputs.privateEndpointSubnetId
privateDnsZoneGroup: {
privateDnsZoneGroupConfigs: [ { privateDnsZoneResourceId: privateDnsZones[blobZoneIndex].outputs.id } ]
}
}
]
diagnosticSettings: [
{ workspaceResourceId: monitoring.outputs.id }
]
}
}
The consuming code changes too — AVM's output names are standardised and differ from the hand-written module's:
| Hand-written | AVM |
|---|---|
storage.outputs.name |
storage.outputs.name |
storage.outputs.blobEndpoint |
storage.outputs.serviceEndpoints.blob |
storage.outputs.containerName |
(not exposed — keep the literal) |
Restore the registry cache and compile:
Then diff against Azure:
az deployment sub what-if --location westeurope --template-file infra/main.bicep --parameters infra/main.dev.bicepparam --exclude-change-types Ignore NoChange
12.3 What the diff shows¶
The AVM module produces more than the hand-written one, and that is the point. Expect to see additions such as:
- a customer-managed key scaffold and identity plumbing left inert
supportsHttpsTrafficOnly,allowCrossTenantReplicationand other properties set to their recommended values explicitly rather than by default- richer diagnostic settings, including metrics categories the hand-written module skipped
- an interface for RBAC assignments, private endpoints and locks that is identical across every AVM module
That last one is the real value. Once you know AVM's privateEndpoints and
diagnosticSettings parameter shapes, you know them for Key Vault, for PostgreSQL, for
everything — instead of learning three slightly different hand-written conventions.
12.4 When to use which¶
Reach for AVM when you are building something real, you want other people to review it against a known standard, you need coverage of properties you have not thought about yet, or you want the Well-Architected defaults without arguing about them.
Write it yourself when you are learning — an AVM call teaches its own parameter schema
and nothing about pickHostNameFromBackendHttpSettings or why a DNS zone group matters —
when the module is a thin wrapper over one resource with three properties, or when you need
behaviour AVM deliberately does not have.
The honest position on this repository: in production it would use AVM for storage, Key Vault, Log Analytics and the VNets, and keep hand-written modules for the Application Gateway and the App Service, where the configuration is the architecture. It is hand-written throughout here because a lab where the interesting parts happen inside someone else's module teaches parameter passing.
12.5 Version pinning is not optional¶
There is no latest tag, and that is deliberate. A floating version means a build that
passed yesterday can fail today for reasons nobody changed. Pin, and upgrade on purpose:
curl -s https://mcr.microsoft.com/v2/bicep/avm/res/storage/storage-account/tags/list | python -m json.tool
az deployment sub what-if --location westeurope --template-file infra/main.bicep --parameters infra/main.dev.bicepparam
Read the diff before merging the bump. AVM modules are pre-1.0 and minor versions do change behaviour.
Exercises¶
-
Do the swap, run what-if, and write down three properties AVM sets that the hand-written module did not. Decide for each whether you want it.
-
Swap the Key Vault too —
br/public:avm/res/key-vault/vault— and discover the conflict with chapter 03: AVM's defaults lean towardpublicNetworkAccess: Disabled, which breaks secret creation during deployment. Work out which parameter restores it. -
Look at a pattern module.
br/public:avm/ptn/…contains whole architectures. Find one close to this platform and compare its decisions with the ones in chapter 00. -
Revert. Put the hand-written module back and confirm what-if reports no change to the properties that matter. Being able to move between the two is the skill worth having.
Checkpoint¶
- The AVM version is pinned, and you know how to find the current one
-
az bicep restoresucceeds and the template compiles against the registry - You can name three differences the swap introduced
- You have an opinion on which modules in this repository should be AVM
Back to the README · or on to troubleshooting and cost and cleanup.