Week 14 - Infrastructure as Code

Why use Infrastructure as Code

IaC concepts

Azure Bicep

Bicep in practice

Deploy Bicep from CI/CD

Practice

Assignment

Gotchas & Pitfalls

Glossary: Week 14

Career relevance: Week 14

History of IaC

Slides (PDF)

Modules at scale

This page is optional. Nothing here is required for Week 14's learning goals or the assignment. Back to the Going Further overview.

In Bicep in practice you split one storage account into modules/storage.bicep and called it from main.bicep. That is the whole idea of a module: a file that takes parameters, declares resources, and returns outputs. This page is about what happens to that idea when one repo becomes ten.

The problem with a local module

module storage 'modules/storage.bicep' is a relative path. It works because the module sits in the same repository as the template calling it. The moment a second team wants your storage module, the options are all bad: copy the file, add a git submodule, or vendor it into a shared folder nobody owns. Copies drift, and a fix to one copy never reaches the others.

Registries

A Bicep module registry solves this by publishing modules to an Azure Container Registry, versioned like container images. You publish once, and every template references the same artifact by version:

module storage 'br:myregistry.azurecr.io/bicep/modules/storage:v1.2.0' = {
  name: 'storageDeploy'
  params: {
    location: location
    storageName: storageName
  }
}

The br: prefix means Bicep registry. The version tag is the important part: a template pinned to v1.2.0 keeps working when the module author ships v1.3.0, and upgrading becomes a deliberate one-line change you can review in a pull request. That is the same habit as pinning a Python dependency instead of tracking main.

Aliases

Typing the full registry URL in every module call gets old. bicepconfig.json lets you alias it, so the reference reads br/modules:storage:v1.2.0 instead. Same mechanism as a package index URL living in config rather than in every import.

Azure Verified Modules

You do not have to write the storage module at all. Azure Verified Modules (AVM) is a Microsoft-owned initiative that publishes pre-built, supported modules encoding Microsoft's own guidance for each resource type. It covers both Bicep and Terraform, and modules come in three flavours:

Type What it gives you
Resource modules One Azure resource, configured to Microsoft's recommended defaults
Pattern modules A reusable architecture made of several resources wired together
Utility modules Supporting helpers

The value is not saving typing. It is that a verified storage module already carries the settings you did not know to think about: TLS version, public network access, diagnostic settings, managed identity wiring. The five-line storage account you wrote this week is correct for a class exercise and thin for production.

The trade-off is real, though, and worth saying plainly: a verified module is a large, heavily parameterised file. Reading one is a poor way to learn what a storage account is, which is exactly why this week has you write the small version by hand first. Reach for AVM when you are shipping infrastructure, not when you are learning the resource.

When to split a module at all

A module earns its place when the same shape is deployed more than once, or when main.bicep has grown past the point where you can see its structure in one screen. Two habits worth copying:

Splitting too early is its own cost. A single-file template that fits on a page is easier to read than three files that each explain one third of a resource.

What this looks like at company scale

The endpoint of this pattern is that you stop writing the common modules at all. Microsoft maintains Azure Verified Modules, production-grade Bicep modules published for Azure services, in the open at Azure/bicep-registry-modules.

There are 77 resource modules, and the list reads like a data platform:

avm/res/storage/storage-account
avm/res/db-for-postgre-sql/flexible-server
avm/res/key-vault/vault
avm/res/databricks/workspace
avm/res/data-factory/factory
avm/res/event-hub/namespace

Consuming one uses the same module keyword you already know. Only the path changes:

module storage 'br/public:avm/res/storage/storage-account:0.33.0' = {
  name: 'storageDeploy'
  params: {
    name: storageName
    location: location
  }
}

br/public: means the public Bicep registry rather than a file on your disk, and the trailing 0.33.0 pins a version, so an upstream change cannot alter your deploy until you bump it. Everything else, name and params, is what you wrote in Chapter 4.

That is the honest ceiling of this skill. A platform team writes the same lines you write, agrees where files go, and then stops writing the ones somebody has already written and tested.

<aside> 🤓 Curious Geek: the reference architecture that got retired

Until recently the answer here was Microsoft's Cloud-scale Analytics reference implementation, a set of repos (data-landing-zone, data-management-zone, data-product-batch) that deployed a whole enterprise data platform in Bicep. Microsoft deprecated the scenario and archived those repos in 2026, folding the guidance into "Unify your data platform" in the Cloud Adoption Framework. It is a useful reminder that reference architectures age faster than the language they are written in: the Bicep in those repos still reads fine, but nobody is maintaining the design decisions around it.

</aside>


The HackYourFuture curriculum is licensed under CC BY-NC-SA 4.0 https://hackyourfuture.net/

CC BY-NC-SA 4.0 Icons

Built with ❤️ by the HackYourFuture community · Thank you, contributors

Found a mistake or have a suggestion? Let us know in the feedback form.