“Wait… now we need networks in every country, plus a list of server URLs for the devs?”

Me: Sounds like a job for Bicep loops. 🛠️😎


💡 Scenario

We’re expanding the smart teddy bear app to multiple countries. That means:

  • We need a virtual network (VNet) in each location.
  • Each VNet should have subnets for the frontend and backend.
  • Devs also want a list of SQL server FQDNs after deployment so they can connect from anywhere.

Let’s break it down and automate it 💪


🚧 What You’ll Learn

Feature Why It Matters
Variable Loops Define repeating items like subnets dynamically
Output Loops Return info (like FQDNs) for each location
Modular Deployment Keeps things clean and scalable

🧩 Step 1: Add Network Config to Your Bicep Template

Open main.bicep.

➕ Add These Parameters:

@description('IP range for the virtual networks')
param virtualNetworkAddressPrefix string = '10.10.0.0/16'

@description('Subnets for each VNet')
param subnets array = [
  {
    name: 'frontend'
    ipAddressRange: '10.10.5.0/24'
  }
  {
    name: 'backend'
    ipAddressRange: '10.10.10.0/24'
  }
]

Think of it like defining rooms inside a house — one house (VNet) per location, each with two rooms (subnets): frontend and backend.


🔁 Step 2: Use a Variable Loop for Subnets

var subnetProperties = [for subnet in subnets: {
  name: subnet.name
  properties: {
    addressPrefix: subnet.ipAddressRange
  }
}]

This loop dynamically builds the list of subnets for each VNet based on our parameter. No more hardcoding!


🏡 Step 3: Deploy One VNet Per Location

Under your existing module databases loop, add this:

resource virtualNetworks 'Microsoft.Network/virtualNetworks@2024-05-01' = [for location in locations: {
  name: 'teddybear-${location}'
  location: location
  properties:{
    addressSpace:{
      addressPrefixes:[
        virtualNetworkAddressPrefix
      ]
    }
    subnets: subnetProperties
  }
}]

Every location in the locations array gets its own teddy-themed VNet 🧸


📬 Step 4: Add Outputs to Your database.bicep Module

Open modules/database.bicep and at the bottom, add:

output serverName string = sqlServer.name
output location string = location
output serverFullyQualifiedDomainName string = sqlServer.properties.fullyQualifiedDomainName

These act like a return statement — giving us back key details about each SQL server.


📦 Step 5: Loop Over Outputs in Your Main Template

Now back in main.bicep, at the bottom, add this:

output serverInfo array = [for i in range(0, length(locations)): {
  name: databases[i].outputs.serverName
  location: databases[i].outputs.location
  fullyQualifiedDomainName: databases[i].outputs.serverFullyQualifiedDomainName
}]

You now get a full list of all deployed SQL server names, their regions, and their FQDNs. Your devs will love you for this 👨‍💻


🧪 Example: Final main.bicep Structure

Your main.bicep will now contain:

  • Secure login parameters
  • Location list
  • VNet and subnet settings
  • Database module loop
  • VNet loop
  • Output loop
param locations array = [ 'westus', 'eastus2', 'eastasia' ]
param sqlServerAdministratorLogin string
param sqlServerAdministratorLoginPassword string
param virtualNetworkAddressPrefix string = '10.10.0.0/16'
param subnets array = [
  { name: 'frontend'; ipAddressRange: '10.10.5.0/24' }
  { name: 'backend'; ipAddressRange: '10.10.10.0/24' }
]

var subnetProperties = [for subnet in subnets: {
  name: subnet.name
  properties: {
    addressPrefix: subnet.ipAddressRange
  }
}]

module databases 'modules/database.bicep' = [for location in locations: {
  name: 'database-${location}'
  params: {
    location: location
    sqlServerAdministratorLogin: sqlServerAdministratorLogin
    sqlServerAdministratorLoginPassword: sqlServerAdministratorLoginPassword
  }
}]

resource virtualNetworks 'Microsoft.Network/virtualNetworks@2024-05-01' = [for location in locations: {
  name: 'teddybear-${location}'
  location: location
  properties: {
    addressSpace: {
      addressPrefixes: [ virtualNetworkAddressPrefix ]
    }
    subnets: subnetProperties
  }
}]

output serverInfo array = [for i in range(0, length(locations)): {
  name: databases[i].outputs.serverName
  location: databases[i].outputs.location
  fullyQualifiedDomainName: databases[i].outputs.serverFullyQualifiedDomainName
}]

🚀 Deploy It!

Make sure you’re set up:

az bicep install && az bicep upgrade
az login
az group create --name BicepRG --location westus

Then deploy:

az deployment group create \
  --resource-group BicepRG \
  --name main \
  --template-file main.bicep

💬 You’ll be prompted for the SQL login and password — make sure they’re secure and meet Azure’s password rules.


🔎 Post-Deployment: What to Check

Head to the Azure Portal:

  • Open your Resource Group
  • You should see three virtual networks (westus, eastus2, eastasia)
  • Each VNet should have frontend and backend subnets
  • Scroll to Deployments > main > Outputs
  • You’ll see an array of your SQL server FQDNs, ready to be copied!

✅ Devs can now grab their connection strings. Ops teams are happy. Mission complete 🎯


🧠 In Short

Feature What We Did
Parameterized Subnets Passed in subnet structure as a parameter
Variable Loop Built reusable subnet configs
Resource Loop Deployed 1 VNet per region
Output Loop Gathered SQL server details for dev handoff

Wanna follow my Azure learning journey?

Stick around — I’m sharing it all, wins and stumbles included 😄

You can find me on LinkedIn — drop me a message and just say hi 👋

Would love to hear what you're working on or learning!