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! 🚀

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

ACI image

ACI.dev: Fully Open-source AI Agent Tool-Use Infra (Composio Alternative)

100% open-source tool-use platform (backend, dev portal, integration library, SDK/MCP) that connects your AI agents to 600+ tools with multi-tenant auth, granular permissions, and access through direct function calling or a unified MCP server.

Check out our GitHub!