Bicep modules: stop copy-pasting your infrastructure
The moment your Bicep grows past a toy, one giant file becomes unreadable and you start copy-pasting the same storage-account block into every deployment. Modules are how infrastructure-as-code learns the lesson every programmer already knows: factor it into reusable functions.
Your first Bicep file is a happy single page. Your tenth is a 900-line monster where the same virtual-network pattern appears three times with tiny differences. That is the exact smell that, in application code, tells you to write a function — and Bicep has the same answer. Microsoft's definition is one sentence: "A module is a Bicep file that another Bicep file deploys." You write a resource pattern once, in its own file, and call it wherever you need it.
Define once, call anywhere
A module is just an ordinary Bicep file — say storageAccount.bicep — with parameters for the bits that vary and outputs for the values callers need back. A parent file consumes it with the module keyword:
module stg './storageAccount.bicep' = { name: 'storageDeploy' params: { storagePrefix: 'examplestg1' location: location } } // use an output the module returned output endpoint object = stg.outputs.storageEndpoint
That is the whole model: the module declares what varies (parameters) and what it hands back (outputs), and the parent supplies the parameters and reads the outputs. The value that comes out — a storage endpoint, a resource ID — flows into the rest of your deployment. It is functions, for infrastructure.
Why it matters
Microsoft names the two payoffs directly: modules "improve the readability of your Bicep files by encapsulating complex details," and you "can easily reuse modules for different deployments." Concretely:
- Readability. The parent file reads like an outline — "deploy the network, then the database, then the app" — with the messy details hidden inside each module.
- Reuse. One well-written
storageAccount.bicepserves dev, test, and prod, and every project, instead of the same block copied and quietly drifting apart in a dozen places. - Encapsulation. Fix a bug or tighten a default once in the module, and every deployment that uses it inherits the fix.
A module is a function for infrastructure: name what varies, return what matters, and stop pasting the same block into every file.
Under the hood, your modules are compiled into a single ARM template with nested deployments, and Resource Manager still works out the ordering. Modules deploy in parallel unless one depends on another's output, in which case Bicep infers the dependency for you. So you get clean, factored source files without giving up the orchestration that makes a deployment reliable.
Sharing modules across a team
Reuse gets serious when a module leaves your repo. Bicep supports two homes for shared modules: a private module registry (backed by an Azure Container Registry, so your team pulls versioned modules like packages), and template specs. There is also a public registry of Azure Verified Modules — prebuilt, pre-tested modules that follow Well-Architected best practices, which you reference by a versioned path (br/public:avm/res/storage/storage-account:...). So before you hand-write the hundredth storage-account module, check whether Microsoft already maintains a verified one.
The takeaway
Modules are the step that turns Bicep from scripting into engineering. When a file gets long or a pattern repeats, extract it: a module with clear parameters and outputs, called from a parent that now reads like a table of contents. Reuse it across environments so a fix lands everywhere at once, share it through a registry or template spec, and reach for Azure Verified Modules before reinventing common ones. "Factor the deployment into parameterized modules, compose them in a parent, and pull common ones from the registry" is the answer of someone who writes infrastructure like software — which, with Bicep, it now is.