DEV Community

1suleyman
1suleyman

Posted on

Creating and Using Bicep Modules – The Smart Way to Scale Your Templates

Hey again! 👋

Today, we're diving into one of Bicep’s most powerful features: modules.

If your main Bicep file is starting to look like a wall of never-ending code, don’t worry — there’s a better way. 🎯

Modules help you break down, reuse, and organize your templates into clean, focused units. Think of them like Lego bricks — build them once, snap them together whenever you need them.

Let’s explore how to create and use Bicep modules with style.


🤔 Why Use Modules?

You've probably already felt the pain of:

  • Massive monolithic templates 🧱
  • Repeating the same code across projects 🔁
  • Difficult-to-navigate resource definitions 😵

Bicep modules fix all that:

✅ Reusability

Create once, reuse across multiple templates or projects.

✅ Encapsulation

Group related resources (like an App Service and its plan) together. Hide the implementation, expose only the essentials.

✅ Composability

Chain modules and pass outputs from one into another. It’s like building infrastructure pipelines from modular blocks.


🛠️ Creating a Module

A module is just another Bicep file. You treat it like any other template — but instead of deploying it directly, you import it into a main file using the module keyword.

Here’s an example:

module appModule 'modules/app.bicep' = {
  name: 'myApp'
  params: {
    location: location
    appServiceAppName: appServiceAppName
    environmentType: environmentType
  }
}
Enter fullscreen mode Exit fullscreen mode

🧾 Breakdown:

  • appModule → symbolic name, used internally (not visible in Azure).
  • 'modules/app.bicep' → path to the Bicep file.
  • params → pass in all required parameters for the module.
  • name → the deployment name (used in Azure’s deployment logs).

🧰 When to Use a Module

Use a module when:

  • You’re grouping related resources (like a full Function App setup).
  • You want to reuse infrastructure patterns (like a common network config).
  • You’re refactoring a massive Bicep file and want better readability.

💡 Don’t go overboard. You don’t need a module for every single resource — group resources that logically belong together.


🔄 Chain Modules Together

Let’s say you want to deploy a VM on a custom VNet. You can:

  1. Create a virtual network module.
  2. Create a VM module.
  3. Output the subnet resource ID from the VNet module and pass it into the VM module.
module virtualNetwork 'modules/vnet.bicep' = {
  name: 'virtual-network'
}

module virtualMachine 'modules/vm.bicep' = {
  name: 'virtual-machine'
  params: {
    adminUsername: adminUsername
    adminPassword: adminPassword
    subnetResourceId: virtualNetwork.outputs.subnetResourceId
  }
}
Enter fullscreen mode Exit fullscreen mode

✅ Bicep knows there’s a dependency and will wait for the VNet to finish before deploying the VM. Neat!


🔧 Add Parameters & Outputs

Each module acts like a little black box. You feed it inputs (parameters), and it can give you outputs in return.

📥 Parameters

You can define them just like in any Bicep file:

@description('The name of the storage account.')
param storageAccountName string
Enter fullscreen mode Exit fullscreen mode

📌 Best practice: Don’t duplicate business rules (like production-only SKUs) inside modules unless it makes sense. Keep your modules generic — let the parent template decide.

📤 Outputs

Useful if the parent needs something back — like a name, ID, or connection string (non-secret!).

@description('The storage account name.')
output storageName string = storageAccount.name
Enter fullscreen mode Exit fullscreen mode

💡 Never output secrets. If you need to handle those, consider Azure Key Vault integration instead.


🔄 Nest Modules

Yes, modules can call other modules. That’s called nesting. But keep it manageable — too many layers, and it becomes spaghetti Bicep 🍝

If your setup gets really complex, it might be better to use deployment pipelines (like in Azure DevOps or GitHub Actions) instead of nesting everything in Bicep.


🔎 How Modules Work Behind the Scenes

  • Every module triggers its own deployment (Microsoft.Resources/deployments).
  • Azure tracks each one separately in deployment history.
  • If you give two deployments the same name, the later one overwrites the first. Use unique names if you want to preserve history.

Bicep transpiles everything into a single JSON ARM template, regardless of how many modules you use. So under the hood, Azure only sees one mega-template.


🧪 Bonus: Add Conditions to Modules

Want to make modules even more dynamic? Combine with if to only deploy under certain conditions:

module logs 'modules/diagnostics.bicep' = if (sendDiagnostics) {
  name: 'diagnostics'
  params: {
    logAnalyticsWorkspaceId: logAnalyticsWorkspaceId
  }
}
Enter fullscreen mode Exit fullscreen mode

🧠 TL;DR – Bicep Modules Recap

  • Group related resources into clean, reusable files.
  • Use parameters/outputs to control and connect your modules.
  • Chain modules by passing outputs from one into another.
  • Use conditions and defaults to make them flexible.
  • Don’t over-nest. Keep it simple and maintainable!

Bicep modules = cleaner templates, reusable patterns, and fewer headaches 😌

Next time your Bicep file starts feeling bloated — split it up. Your future self will thank you 🙏

If this was helpful or if you’ve got Bicep ideas to share, come say hey on LinkedIn. Let’s learn together! 🚀

Top comments (0)