Azure Bicep Advanced Techniques: Mastering Conditional Deployments, Loops, and Dependencies
Welcome back to the Azure Bicep journey. If you’ve followed along from the start, you’ve gone from writing basic templates to designing clean, modular infrastructure. You’ve learned to define parameters, use variables for clarity, and structure your deployments with reusable components. You’ve started thinking like an infrastructure architect—not just someone who writes code, but someone who designs systems.
Now it’s time to bring that foundation to life—with real-world fluency.
This is where we stop treating infrastructure as a static checklist—and start treating it as a living, adaptable system.
The features we explore in this article—conditions, loops, and dependencies—aren’t just technical tools. They’re how modern cloud architectures scale intelligently and adapt to their environment. And that’s exactly the kind of thinking we’ll sharpen in this next phase.
These are the tools that turn good templates into dynamic, composable cloud systems.
Here’s what you’ll learn:
- Deploy resources conditionally based on environment, configuration, or business logic
- Create multiple resources at once using
for
expressions to avoid repetition - Manage dependencies cleanly and explicitly so resources deploy in the right order with minimal guesswork
We’ll go beyond syntax—you’ll learn why each pattern matters, when to use it, and how it fits into a broader way of thinking about cloud architecture.
If you’re part of a team, these patterns will help you understand and reason through infrastructure designed by others. If you’re leading deployments, they’ll give you the tools to evolve your own designs with clarity and purpose.
Ready to dive in? Let’s begin.
Conditional Deployments — Teaching Templates to Make Decisions
In production, you might need a premium SKU. In dev, the basic tier will do. In staging, you might spin up extra diagnostics—but skip them in live environments.
These variations are common. But managing them cleanly in a single template?
That’s where conditional deployments shine.
With just a single line of logic, you can instruct your Bicep templates to include or exclude resources based on parameters, environment, or any condition you define. Your template doesn’t just run—it responds to context.
Think of conditional deployments as a “choose your own adventure” path in your infrastructure.
The diagram below shows how your template can include or skip resources based on logic you define:
Here, the dotted line represents a conditional path between main.bicep
and a resource. It means:
“Only deploy this if a certain condition is met.”
This isn’t just about skipping code—it’s about giving your infrastructure logic that matches your intent.
How It Works
In Bicep, you make a resource deployment conditional by adding an if
clause directly to the declaration:
|
|
This if (...)
clause might look simple, but it’s powerful.
It tells Bicep to skip the entire resource unless the condition is true.
No dummy values, no feature flags—just clean logic and smart behaviour.
Real-World Example: Environment-Specific Logic
Let’s say you’re deploying a web application. You want a staging slot to exist in development and test—but never in production.
Here’s how you’d write that:
|
|
This template always deploys the main web app.
But the staging slot only gets created when environment
is not 'prod'
.
One parameter value. One logic check.
And suddenly, this template adapts cleanly to dev, test, and prod.
🧠 Architectural Insight
This is one of the clearest signs of an architectural mindset:
Separating configuration from behaviour.
Rather than hardcoding logic, drive decisions through parameters like environment
.
This keeps your templates flexible, reusable, and easy to understand—without touching the file for every change.
✅ Best Practices for Conditional Deployments
- Use clear and simple conditions. Avoid nesting or overly clever logic. Prioritise readability.
- Drive logic with parameters. Use parameter-driven logic like
if (environment == 'prod')
—not hardcoded strings or magic values.. - Document your intent. A quick comment like
// Only deploy in non-production
goes a long way in team settings. - Watch for dependent resources. Skipping a resource can break others if they expect it to exist. (We’ll look at how to manage this with dependencies soon.)
- Yes—you can use conditions with modules too.
Just like with resources, you can make a module deployment conditional by applying anif
clause. This makes it easy to include or skip entire parts of your infrastructure—like diagnostics, monitoring, or networking—based on environment or feature flags. We’ll explore this in more detail later in the article.
Thinking Ahead
Conditional deployments help your templates adapt—without branching, duplication, or separate files.
And once your templates can think, the next step is helping them scale.
Let’s explore how to deploy multiple resources at once, cleanly and dynamically—with loops.
Loops — Deploying Multiple Resources, Cleanly and Dynamically
Ever duplicated the same resource block three times with tiny changes—just to deploy to dev, test, and prod?
It works… but it’s tedious, error-prone, and hard to maintain.
That’s not a personal failing—it’s a sign that your template is ready for something more powerful: loops.
Loops let you define once, deploy many.
You write a single resource block and let Bicep repeat it with variations—based on an array of values, a numeric range, or even a dynamic input from a parameter file.
The diagram below shows how your main.bicep
file can define a repeatable pattern—and how Bicep turns that pattern into multiple concrete resources:
Think of it like stamping out consistent infrastructure, one variation at a time:
How It Works
In Bicep, you can loop over values using a for
expression inside your resource declaration:
|
|
This loop creates three separate storage accounts, one for each value in the names
array.
This replaces three nearly identical resource blocks—and makes it easier to:
- Add new environments
- Maintain consistency
- Avoid human error
You’ve gone from copy-paste to repeatable, parameter-driven infrastructure.
🧠 Architectural Insight
If you’ve ever found yourself copying a resource block, tweaking a name, and pasting it again…
You’ve already discovered a pattern.
Loops are how you elevate that pattern into clean, declarative structure—one that grows without getting messy.
This mindset shift—recognising repetition as an opportunity—is what makes your infrastructure scalable by design.
Loop Types in Bicep
Bicep supports two primary loop types. Use them depending on your scenario:
-
Array Loops – iterate over specific values
Example:['dev', 'test', 'prod']
-
Range Loops – repeat a block N times
Example:range(0, 3)
Here’s what a range loop looks like:
|
|
This creates three VMs—
vm0
,vm1
, andvm2
.
Great for test environments, burst workloads, or any repeated infrastructure that just needs a unique name.
✅ Best Practices for Loops
- Use meaningful loop variables. Avoid cryptic names like
i
unless you’re doing simple indexing. Useenv
,name
, orregion
instead. - Drive loop values from parameters. Don’t hardcode arrays unless you’re testing. Let users or environments control behaviour.
- Keep logic readable. You can use
if
conditions inside loops—but if your logic gets messy, it’s a sign to split the pattern. - Don’t assume sequential deployment.
Looped resources may deploy in parallel. If something depends on another, usedependsOn
explicitly.
💡 We’ll dive deeper into dependency management in the very next section.
Thinking Ahead
Loops help your templates scale with intent. They let you say,
“Do this… again, and again—with variation.”
But scale isn’t just about quantity—it’s about coordination.
Some resources must come first. Others must wait.
Let’s look at how to manage the flow and timing of your deployments—with clarity, precision, and confidence.
Dependencies — Orchestrating the Flow of Your Deployments
So far, you’ve taught your templates to adapt (with conditionals) and scale (with loops).
Now comes the final piece of the puzzle: orchestrating the flow of deployment.
Some resources can be deployed in parallel.
Others must wait for something else to finish.
You can’t create a database inside a server until the server exists.
You can’t assign a private endpoint to a storage account until that account has been deployed.
This isn’t just about sequencing—this is about designing infrastructure that knows what depends on what, and behaves accordingly.
That’s where dependencies come in.
Visualising a Dependency
Think of dependencies as the connections between pieces of your infrastructure—lines of trust, timing, and sequence.
The diagram below shows one resource depending on another:
Here, Resource A depends on Resource B.
That means A won’t even begin deploying until B is ready and completed successfully.
Bicep handles this sequencing behind the scenes when it can—but sometimes, you need to step in and define it yourself.
Let’s explore both approaches.
Implicit Dependencies — Let Bicep Do the Work
In most cases, you don’t need to write anything extra.
If one resource references another—by its name, ID, or property—Bicep automatically figures out the order and applies the correct sequencing.
Example:
|
|
In this case, the blobContainer
references storage.name
.
That’s all Bicep needs to know the correct order—no dependsOn
, no extra configuration.
💡 This is one of Bicep’s quiet superpowers.
It reduces the noise and lets your logic express itself naturally.
You describe relationships by referencing them—and Bicep handles the rest.
Explicit Dependencies — When You Need to Be Direct
Sometimes, the relationship between resources isn’t visible through a property.
Maybe you’re using conditionals, modules, or orchestrating components that don’t directly reference one another.
That’s when you step in and say, clearly:
“This resource must wait.”
|
|
Think of implicit dependencies as inferred trust.
Bicep sees the connection and handles it.
But sometimes, you need to write a manual instruction—like saying:
“This dashboard depends on telemetry being ready—even if it’s not obvious from the code.”
🧠 Architectural Insight
Overusing
dependsOn
is like shifting gears in a car that already has a great automatic transmission.
It works—but it’s extra effort you don’t always need.
A clean Bicep template leans on implicit logic wherever possible.
But when the relationship isn’t clear—or when using modules or conditionals—being explicit is not only okay, it’s essential.
Let the system do the heavy lifting, and intervene only when clarity or correctness calls for it.
✅ Best Practices for Managing Dependencies
- Trust implicit logic. If Bicep sees a reference, it will enforce the correct order automatically.
- Be explicit when necessary. Especially in complex templates with conditionals or loosely coupled resources.
- Avoid circular references. If A depends on B, and B depends on A—your deployment will fail.
- Group related resources logically. If several things depend on a shared component, like a VNet, consider placing them together.
- Document your intent. If you’re using
dependsOn
, include a comment explaining why.
🔍 Ask yourself: Would this still work if I removed this
dependsOn
?
If yes—you probably don’t need it.
Thinking Ahead
With conditions, your templates can make decisions.
With loops, they can scale patterns.
And with dependencies, they orchestrate complex systems with clarity and precision.
You’re not just listing resources anymore.
You’re designing an infrastructure that adapts, scales, and coordinates—like a well-conducted symphony.
Now, let’s put it all together.
In the next section, we’ll walk through a real-world scenario that uses conditions, loops, and dependencies in a unified, practical template.
Putting It All Together — A Real-World Scenario
You’ve learned how to adapt your templates with conditionals, scale them with loops, and control flow with dependencies.
Now it’s time to think like a system designer.
This isn’t just an example. It’s your first step toward building infrastructure that’s reusable, modular, and architected with intent.
The Scenario: A Multi-App, Multi-Environment Deployment
You’re working on a small platform team managing shared infrastructure for internal projects.
Each project requires:
- A shared App Service Plan
- Multiple Web Apps for different stages (dev, test, prod)
- A single, shared SQL Server and Database
- Optional Application Insights—but only in production, where monitoring really matters
Your goal is to build a single Bicep template that:
- Scales across app environments using a loop
- Deploys App Insights only when needed
- Coordinates shared resources with dependencies
- Outputs key values for integration into downstream systems
In short: you’re building a real-world system, not just deploying resources.
How the Techniques Interact
Technique | Purpose |
---|---|
Loop | Deploy multiple apps cleanly |
Condition | Control App Insights based on context |
Dependency | Ensure plan, DB, and telemetry are ready when needed |
Step 1: Looping Over Web Apps
Let’s start with a loop to deploy web apps for dev
, test
, and prod
—each using the same shared plan.
|
|
This defines a reusable pattern that adapts based on input, while anchoring every app to a shared plan.
Step 2: Conditionally Deploying Application Insights
Application Insights is crucial for monitoring in production, but we might skip it in dev/test to reduce noise and cost.
|
|
This condition ensures telemetry is added only when it adds value—and keeps the rest of the template untouched.
Step 3: Shared SQL Server and Database
Let’s define a shared SQL Server and a database that all apps can use.
|
|
⚠️ The admin password is hardcoded here for simplicity—but in production, you’d parameterise this or pull it securely from Azure Key Vault.
Step 4: Outputs and Orchestration
Finally, let’s return the important pieces that other systems or pipelines might need.
|
|
Bicep handles the dependencies automatically here:
- Web Apps depend on the App Service Plan → ✅
- SQL DB depends on the SQL Server → ✅
- App Insights is conditional → deployed only in production
✅ No need for
dependsOn
unless you’re managing complex or decoupled scenarios. Let Bicep do the sequencing for you.
🧠 Design Reflections
This isn’t just a demo—it’s a platform pattern.
- Loops give you scale without duplication
- Conditions make your infrastructure context-aware
- Dependencies ensure things happen in the right order
- Outputs connect your templates to the rest of your deployment lifecycle
You’re not building a file—you’re designing modular infrastructure that adapts and evolves.
Want to Make It Yours?
Try one of these next steps to take this design from solid to scalable:
- Turn the looped app block into a reusable module
- Secure the SQL credentials via Key Vault integration
- Move the shared infrastructure to a base layer and use nested deployments
- Add dynamic tags per environment using
for
and object literals
Each one deepens your fluency and gives you more confidence with real-world Bicep design.
Onward
With just a few techniques, you’ve gone from defining infrastructure to orchestrating systems.
You’re not just writing Bicep—you’re designing infrastructure that adapts, scales, and speaks for itself.
You’ve got everything you need. Let’s bring it all home in the conclusion.
Conclusion & What’s Next
Think back to when you first began this journey.
Back then, Bicep was just syntax, and infrastructure felt like merely a list of resources.
Now, look at you—you haven’t just learned Bicep; you’ve completely transformed how you think about building cloud systems.
You’ve grown from individual features—parameters, variables, modules—to a unified, orchestrated approach to infrastructure, using:
- Conditionals to make your templates adaptive to real-world scenarios.
- Loops to scale your deployments effortlessly.
- Dependencies to orchestrate complex infrastructure precisely.
- And finally, a real-world scenario that pulled all these skills together into a cohesive design.
But beyond syntax and features, you’ve practiced the mindset of an infrastructure designer—someone who writes code not just to provision, but to model systems with clarity, reuse, and intent.
What Comes Next?
If this series marks your graduation from “Bicep user” to “system designer,” here’s how to keep the momentum going:
-
Build Reusable Modules
Start small—identify one resource you repeatedly deploy. Turn it into your first reusable module, share it with your team, and watch your impact multiply. -
Integrate with Pipelines
Automate a template you built with GitHub Actions or Azure DevOps. Witness firsthand how fast your infrastructure moves from code to reality. -
Apply Governance
Try adding tags or enforcing naming standards in an existing deployment. You’ll quickly realise governance becomes second nature when embedded directly into your templates. -
Use Module Registries
Publish your best modules internally via a Bicep registry or Azure Container Registry. Watch them spread through your team or organisation, lifting everyone’s productivity.
Each step will deepen your mastery, multiply your impact, and elevate your work from tactical scripting to strategic system orchestration.
Final Reflections
Infrastructure-as-Code is no longer about “writing resources.” It’s about designing systems that can evolve with your team, your workloads, and your business.
You now have the mindset of an infrastructure architect—someone who sees not just the pieces, but the patterns.
So whether you’re:
- Reviewing a complex template and reasoning through its flow
- Designing new systems for scale
- Teaching others to structure infrastructure with confidence…
Remember:
🧠 Clarity beats complexity. Reusability scales. Structure is strategy.
This isn’t the end of your journey.
It’s the beginning of what you can build—with intent, with confidence, and with Bicep.
Thank you 🙌 for joining me on this journey. And happy authoring. You’ve got this. 💪