Azure Bicep is Microsoft's domain-specific language for deploying Azure infrastructure as code. This roadmap takes you from writing your first template through expressions and parameters, deploying real resources like VNets and VMs, managing resource lifecycles with deployment stacks, and finally writing production-ready code with conditions, loops, and what-if previews. Each phase builds on the last — with a hands-on lab to lock in the skill before you move on.
🗺️ How This Roadmap Works
This isn't a documentation dump. It's a structured learning path designed the way I teach Bicep in my training sessions — concept first, then hands-on, then build.
Each phase follows this pattern:
- 📚 Understand the concept — what it is and why it matters
- 🧪 Do the lab — hands-on practice in a real Azure environment
- 🏗️ Apply it — what you should be able to build after this phase
- ✅ Checkpoint — how to know you're ready to move on
Estimated total time: 2–3 weeks at 1–2 hours per day. If you already work with ARM templates or Terraform, you'll move faster.
Don't skip phases. I've seen engineers jump straight to deployment stacks and get confused because they didn't build the muscle memory for parameters and expressions first. Each phase takes less than a day — invest the time.
🧭 Why Bicep (and Why Now)
If you work in Azure, Bicep should be in your toolkit. Here's why:
| ARM Templates / Portal | Bicep |
|---|---|
| JSON — verbose, hard to read | Clean, concise syntax |
| No native modularity | First-class module support |
| Manual dependency management | Automatic dependency detection |
| Copy-paste between environments | Parameters + expressions = reusable |
| No deployment lifecycle management | Deployment stacks manage full lifecycle |
Bicep compiles down to ARM templates, so you get the same deployment engine — but with a language that's actually pleasant to write. And unlike Terraform, there's zero state file management. Azure IS the state.
Every AZ-104, AZ-400, and AZ-305 exam now expects you to understand Bicep. More importantly, every Azure job posting in 2026 lists IaC as a requirement. This is the skill that separates "I know Azure" from "I can build and ship in Azure."
🚀 Phase 1: Write and Deploy Your First Template
⏱️ Time: 2–3 hours | 🎯 Goal: Deploy a real Azure resource using Bicep
Before you can build anything complex, you need to understand the fundamentals: how a Bicep file is structured, how to deploy it, and what happens under the hood.
What You'll Learn
| Concept | Why It Matters |
|---|---|
| Bicep file structure | Every .bicep file follows the same pattern — resources, parameters, outputs |
| Resource declarations | The core building block — resource keyword + symbolic name + type + properties |
| The deployment workflow | az deployment group create — the command you'll run hundreds of times |
| What Bicep compiles to | Understanding that Bicep → ARM JSON helps you debug issues |
The Core Pattern
Every Bicep deployment follows this flow:
write .bicep file → az deployment group create → Azure Resource Manager → resources created
Your first template should be simple — a storage account is perfect. It has minimal required properties but teaches you the full deployment cycle.
// main.bicep — your first template
param location string = resourceGroup().location
param storageAccountName string = 'stdemobicep${uniqueString(resourceGroup().id)}'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccountName
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
output storageId string = storageAccount.idThen deploy it:
az deployment group create \
--resource-group rg-bicep-demo \
--template-file main.bicepThat's it. You just deployed infrastructure as code.
🧪 Hands-On Lab

Lab: Write & Deploy Your First Bicep Template
Start from scratch — create a Bicep file, understand the syntax, and deploy your first Azure resource. This is where your IaC journey starts.
✅ Phase 1 Checkpoint
You're ready for Phase 2 when you can:
- Create a
.bicepfile from scratch without copying from docs - Explain what
resource,param, andoutputdo - Deploy a template using
az deployment group create - Find and read the deployment output in the terminal
🔧 Phase 2: Expressions, Parameters, Variables, and Outputs
⏱️ Time: 3–4 hours | 🎯 Goal: Write reusable, flexible templates
Phase 1 got you deploying. Phase 2 makes your templates smart. This is where Bicep stops feeling like a chore and starts feeling powerful.
What You'll Learn
| Concept | Why It Matters |
|---|---|
| Parameters | Accept input values — makes templates reusable across environments |
| Variables | Compute values once, reference them everywhere — keeps templates DRY |
| Outputs | Return values after deployment — critical for chaining deployments |
| Expressions & functions | uniqueString(), resourceGroup().location, string interpolation — the glue that makes Bicep flexible |
| Decorators | @allowed, @minLength, @description — add validation and documentation inline |
Why This Phase Matters
Without parameters and variables, every environment gets a separate template. That's a maintenance nightmare. With them, you write one template and deploy it to dev, staging, and production by changing a parameter file.
// parameters make templates reusable
@allowed(['dev', 'staging', 'prod'])
param environment string
@description('The Azure region for all resources')
param location string = resourceGroup().location
// variables compute values from parameters
var storageName = 'st${environment}${uniqueString(resourceGroup().id)}'
var skuName = environment == 'prod' ? 'Standard_GRS' : 'Standard_LRS'
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageName
location: location
sku: {
name: skuName
}
kind: 'StorageV2'
tags: {
environment: environment
}
}
// outputs let you reference deployed resource properties
output storageEndpoint string = storageAccount.properties.primaryEndpoints.blobNotice how the same template handles dev (cheap, LRS) and prod (geo-redundant, GRS) — just by changing the environment parameter.
🧪 Hands-On Lab

Lab: Bicep Expressions, Parameters, Variables & Outputs
Build templates that adapt to different environments using parameters, variables, built-in functions, and outputs. The foundation for every real-world Bicep project.
✅ Phase 2 Checkpoint
You're ready for Phase 3 when you can:
- Create a parameter with validation decorators (
@allowed,@minLength) - Use
uniqueString()and string interpolation to generate resource names - Explain the difference between a parameter and a variable
- Deploy the same template to two different environments using parameter values
🏗️ Phase 3: Deploy Real Infrastructure — VNets and VMs
⏱️ Time: 3–4 hours | 🎯 Goal: Deploy multi-resource infrastructure with dependencies
Deploying a storage account proves you can use Bicep. Deploying a VNet with subnets and a VM proves you can build real infrastructure. This is the phase where your templates start looking like something you'd actually use at work.
What You'll Learn
| Concept | Why It Matters |
|---|---|
| Multi-resource templates | Real deployments have 5, 10, 50+ resources — you need to organize them |
| Resource dependencies | Bicep auto-detects most dependencies, but you need to understand when (and why) to use dependsOn |
| Networking resources | VNets, subnets, NSGs, NICs, public IPs — the networking stack you'll deploy in every project |
| Virtual machines | The most common Azure resource — combining compute, storage, and networking |
| Resource references | Using existingResource.id to wire resources together |
The Real-World Pattern
In practice, a VM deployment isn't one resource — it's a stack:
VNet → Subnet → NSG → Public IP → NIC → VM
Bicep handles the dependency graph automatically when you reference one resource inside another. That's one of its biggest advantages over ARM JSON, where you had to manually specify dependsOn for everything.
// Bicep figures out that the NIC depends on the subnet
// because you referenced subnet.id in the NIC properties
resource nic 'Microsoft.Network/networkInterfaces@2023-11-01' = {
name: 'nic-${vmName}'
location: location
properties: {
ipConfigurations: [
{
name: 'ipconfig1'
properties: {
subnet: {
id: vnet.properties.subnets[0].id // ← implicit dependency
}
privateIPAllocationMethod: 'Dynamic'
}
}
]
}
}No dependsOn needed. Bicep sees the reference and orders the deployment correctly.
🧪 Hands-On Lab

Lab: Deploy a Virtual Network & VM Using Bicep
Build a complete networking stack and deploy a virtual machine — VNet, subnet, NSG, public IP, NIC, and VM — all in one Bicep template. This is what real Azure infrastructure looks like.
✅ Phase 3 Checkpoint
You're ready for Phase 4 when you can:
- Deploy a template with 5+ resources that depend on each other
- Explain how Bicep resolves resource dependencies automatically
- Deploy a VM you can actually SSH or RDP into
- Identify when you do need an explicit
dependsOn
VMs incur costs even when stopped (disk storage still charges). Always clean up after labs: az group delete --name rg-bicep-vm --yes --no-wait. Or better yet — learn deployment stacks in Phase 4, which handle cleanup for you.
📦 Phase 4: Deployment Stacks — Manage the Full Resource Lifecycle
⏱️ Time: 3–4 hours | 🎯 Goal: Control what happens to resources when your template changes
This is the phase most Bicep tutorials skip — and it's the one that matters most in production.
Here's the problem: you deploy 10 resources with Bicep. Next week, you remove 2 resources from your template and redeploy. What happens to those 2 resources? With a standard deployment — nothing. They stay in Azure, unmanaged, costing you money. They become drift.
Deployment stacks fix this.
What You'll Learn
| Concept | Why It Matters |
|---|---|
| Deployment stacks | A management layer that tracks which resources belong to your template |
| actionOnUnmanage | What happens when a resource is removed from the template — delete, detach, or deleteAll |
| denySettings | Lock deployed resources to prevent manual portal changes (goodbye config drift) |
| Stack updates | Update your infrastructure safely by updating the stack, not just redeploying |
| Resource lifecycle | The full picture: create → update → remove → clean up |
Why This Changes Everything
Without deployment stacks, Bicep is a deployment tool. With them, it's a management tool. Think of it like the difference between putting furniture in a room (deployment) vs. having an inventory system that knows what's there and removes what shouldn't be (stacks).
# Create a deployment stack
az stack group create \
--name demo-stack \
--resource-group rg-bicep-stacks \
--template-file main.bicep \
--action-on-unmanage deleteAll \
--deny-settings-mode denyWriteAndDelete
# Now if you remove a resource from main.bicep and re-run...
# the stack DELETES the removed resource from Azure automaticallyThe --deny-settings-mode denyWriteAndDelete is the real power move — it prevents anyone from modifying or deleting your resources through the portal or CLI. Your Bicep template becomes the single source of truth.
🧪 Hands-On Lab

Lab: Bicep Deployment Stacks & Resource Lifecycle Management
Go beyond basic deployments. Learn how deployment stacks track, protect, and clean up your Azure resources — the feature that makes Bicep production-ready.
✅ Phase 4 Checkpoint
You're ready for Phase 5 when you can:
- Explain the difference between a standard deployment and a deployment stack
- Create and update a deployment stack from the CLI
- Configure
actionOnUnmanageand explain what each option does - Apply deny settings to prevent manual changes to deployed resources
🎯 Phase 5: Conditions, Loops, and What-If — Production Patterns
⏱️ Time: 3–4 hours | 🎯 Goal: Write production-grade Bicep with conditional logic and safe deployment previews
This is where you go from "I can deploy things" to "I write infrastructure code that teams trust in production." Conditions let your templates adapt. Loops eliminate repetition. What-if lets you preview changes before they touch anything.
What You'll Learn
| Concept | Why It Matters |
|---|---|
| Conditional deployments | Deploy resources only when certain criteria are met — skip dev-only or prod-only resources |
Loops (for) | Deploy multiple similar resources without copy-pasting resource blocks |
| What-if previews | See exactly what will change BEFORE deploying — the safety net every team needs |
| Combining patterns | Real templates use conditions + loops + parameters together |
Conditions — Deploy Smarter, Not Harder
@allowed(['dev', 'staging', 'prod'])
param environment string
// Only deploy monitoring in staging and production
resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2023-09-01' = if (environment != 'dev') {
name: 'log-${environment}'
location: resourceGroup().location
properties: {
sku: {
name: 'PerGB2018'
}
retentionInDays: environment == 'prod' ? 90 : 30
}
}One template. Three environments. The right resources in each — automatically.
Loops — Stop Repeating Yourself
param subnetNames array = ['web', 'app', 'data']
resource vnet 'Microsoft.Network/virtualNetworks@2023-11-01' = {
name: 'vnet-main'
location: resourceGroup().location
properties: {
addressSpace: { addressPrefixes: ['10.0.0.0/16'] }
subnets: [for (name, i) in subnetNames: {
name: 'snet-${name}'
properties: {
addressPrefix: '10.0.${i}.0/24'
}
}]
}
}Three subnets, zero duplication. Need six subnets next month? Update the array.
What-If — Preview Before You Break Things
az deployment group what-if \
--resource-group rg-production \
--template-file main.bicep \
--parameters environment=prod
# Output shows:
# + Create: logAnalytics (new)
# ~ Modify: storageAccount (sku changed)
# - Delete: nothingWhat-if is the command I run before every production deployment. It shows exactly what will be created, modified, or deleted — without touching anything. If the output looks wrong, you fix the template. If it looks right, you deploy with confidence.
🧪 Hands-On Lab

Lab: Bicep Conditions, Loops, and What-If Deployments
Write production-grade templates with conditional logic, deploy resources in loops, and preview changes safely with what-if. The patterns that make teams trust your infrastructure code.
✅ Phase 5 Checkpoint
You've completed the roadmap when you can:
- Conditionally deploy resources based on environment parameters
- Use
forloops to deploy multiple resources from an array - Run
what-ifand correctly interpret the output - Explain why you'd use what-if in a CI/CD pipeline before
az deployment group create
🗺️ The Complete Learning Path — At a Glance
| Phase | You Learn | You Can Build | Lab |
|---|---|---|---|
| 1. First Template | Resource declarations, deploy workflow | A storage account via Bicep | Start |
| 2. Expressions & Params | Parameters, variables, functions, outputs | Reusable multi-environment templates | Start |
| 3. Real Infrastructure | Dependencies, networking, VMs | A full VNet + VM stack | Start |
| 4. Deployment Stacks | Lifecycle management, deny settings | Self-cleaning, drift-protected infra | Start |
| 5. Production Patterns | Conditions, loops, what-if | Safe, adaptable production deployments | Start |
What's Next
After completing this roadmap, you're ready for:
- Bicep modules — break large templates into reusable, versioned components
- CI/CD pipelines — deploy Bicep through GitHub Actions or Azure DevOps with what-if gates
- Bicep parameter files —
.bicepparamfiles for managing environment-specific values - Template specs — publish and share templates across teams and subscriptions
The engineers who ship reliable infrastructure aren't using different tools than you — they're using the same tools with more structure. This roadmap gave you that structure. Now go build.

CloudLearn — Hands-On Azure Labs
Every lab in this roadmap is available on CloudLearn. Guided, browser-based Azure labs — no setup, no credit card, just learn by doing.