🚀 “The toy wombat website needs a CDN!”

“Other teams don’t need it.”

Me: “No problem – I’ll make it optional.”


🎯 The Mission

We're launching a website for the new toy wombat — and to handle traffic spikes, we’ll add a Content Delivery Network (CDN).

But... only if needed. Other teams can skip it.

So let’s build this smart and reusable:

  • 📦 Use modules for the App Service and CDN
  • ✅ Make the CDN optional
  • 🌍 Output the right hostname depending on what’s deployed

🧱 Step 1: Create the App Module

In VS Code:

  1. Create a folder called modules
  2. Inside it, create a file: app.bicep

Paste in this logic:

@description('The Azure region into which the resources should be deployed.')
param location string

@description('The name of the App Service app.')
param appServiceAppName string

@description('The name of the App Service plan.')
param appServicePlanName string

@description('The name of the App Service plan SKU.')
param appServicePlanSkuName string

resource appServicePlan 'Microsoft.Web/serverfarms@2024-04-01' = {
  name: appServicePlanName
  location: location
  sku: {
    name: appServicePlanSkuName
  }
}

resource appServiceApp 'Microsoft.Web/sites@2024-04-01' = {
  name: appServiceAppName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
  }
}

@description('The default host name of the App Service app.')
output appServiceAppHostName string = appServiceApp.properties.defaultHostName

What this does:

It spins up a basic App Service Plan + App. Outputs the app’s host name.


💻 Step 2: Set Up Your Main Template

Create a file called main.bicep.

Add these parameters:

param location string = 'westus3'
param appServiceAppName string = 'toy-${uniqueString(resourceGroup().id)}'
param appServicePlanSkuName string = 'F1'
param deployCdn bool = true

Add a variable:

var appServicePlanName = 'toy-product-launch-plan'

🧩 Step 3: Use the App Module

Still in main.bicep, insert this module:

module app 'modules/app.bicep' = {
  name: 'toy-launch-app'
  params: {
    appServiceAppName: appServiceAppName
    appServicePlanName: appServicePlanName
    appServicePlanSkuName: appServicePlanSkuName
    location: location
  }
}

🌐 Step 4: Create the CDN Module

Create a new file inside modules called cdn.bicep.

Paste this code:

param originHostName string
param profileName string = 'cdn-${uniqueString(resourceGroup().id)}'
param endpointName string = 'endpoint-${uniqueString(resourceGroup().id)}'
param httpsOnly bool

var originName = 'my-origin'

resource cdnProfile 'Microsoft.Cdn/profiles@2024-09-01' = {
  name: profileName
  location: 'global'
  sku: {
    name: 'Standard_Microsoft'
  }
}

resource endpoint 'Microsoft.Cdn/profiles/endpoints@2024-09-01' = {
  parent: cdnProfile
  name: endpointName
  location: 'global'
  properties: {
    originHostHeader: originHostName
    isHttpAllowed: !httpsOnly
    isHttpsAllowed: true
    queryStringCachingBehavior: 'IgnoreQueryString'
    contentTypesToCompress: [
      'text/plain'
      'text/html'
      'text/css'
      'application/x-javascript'
      'text/javascript'
    ]
    isCompressionEnabled: true
    origins: [
      {
        name: originName
        properties: {
          hostName: originHostName
        }
      }
    ]
  }
}

output endpointHostName string = endpoint.properties.hostName

What this does:

It creates a CDN profile + endpoint, pointing to your app. Outputs the CDN host name.


🧩 Step 5: Use the CDN Module (With a Condition)

Back in main.bicep, add the CDN module:

module cdn 'modules/cdn.bicep' = if (deployCdn) {
  name: 'toy-launch-cdn'
  params: {
    httpsOnly: true
    originHostName: app.outputs.appServiceAppHostName
  }
}

💡 This only runs if deployCdn is true.


📦 Step 6: Output the Right Hostname

Now, let’s give the user the correct URL based on whether CDN is on:

output websiteHostName string = deployCdn ? cdn.outputs.endpointHostName : app.outputs.appServiceAppHostName

✅ Final main.bicep Snapshot

param location string = 'westus3'
param appServiceAppName string = 'toy-${uniqueString(resourceGroup().id)}'
param appServicePlanSkuName string = 'F1'
param deployCdn bool = true

var appServicePlanName = 'toy-product-launch-plan'

module app 'modules/app.bicep' = {
  name: 'toy-launch-app'
  params: {
    appServiceAppName: appServiceAppName
    appServicePlanName: appServicePlanName
    appServicePlanSkuName: appServicePlanSkuName
    location: location
  }
}

module cdn 'modules/cdn.bicep' = if (deployCdn) {
  name: 'toy-launch-cdn'
  params: {
    httpsOnly: true
    originHostName: app.outputs.appServiceAppHostName
  }
}

output websiteHostName string = deployCdn ? cdn.outputs.endpointHostName : app.outputs.appServiceAppHostName

🚀 Deploy It!

Make sure you're ready:

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

Then deploy:

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

🔎 Check the Deployment

In the Azure Portal:

  • Go to Resource Groups > BicepRG
  • Go to Deployments > main
  • Expand it to see both modules: toy-launch-app and (if enabled) toy-launch-cdn
  • Check Outputs:
    • If CDN is enabled, you’ll get the CDN host name
    • Otherwise, you get the App Service host name

Try both in the browser (add https:// in front)!

📌 CDN might take a minute to activate — if it doesn't load instantly, just wait and retry.


🧠 In Short

Feature What We Did
App Module Reusable App Service plan + app deployment
CDN Module Created a globally distributed cache layer
Conditional Module Made CDN deploy only when needed
Smart Output Automatically return the correct public URL

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!